Xamarin incrementing loop of chat bubble feature - c#

I'm creating a chatbot app with Xamarin.Forms, when I send a message and the chatbot replies the chat bubbles kinda do this incrementing looping stack thing. The first reply has one response, the second one has two, the third one has three and so on.
I've been using a sorta obscure bot framework called Oscova Bot and after checking for an answer on their forums I think it has something to do with an event handler and it occuring more than once or something.
Heres the C# code.
/// <summary>
/// View model for the ChatbotPage.xaml
/// </summary>
public class ChatbotPageViewModel : BaseViewModel
{
/// <summary>
/// A field for TextToSend
/// </summary>
private string _texttosend;
/// <summary>
/// An Instance of a new SIML Oscova Chatbot
/// </summary>
public OscovaBot chatbot;
/// <summary>
/// A collection/list of chat message items
/// </summary>
public ObservableCollection<ChatMessageModel> Messages { get; set; } = new ObservableCollection<ChatMessageModel>();
/// <summary>
/// The text that the user inputs
/// </summary>
public string TextToSend
{
get
{
return _texttosend;
}
set
{
if (_texttosend != value)
{
_texttosend = value;
OnPropertyChanged();
}
}
}
/// <summary>
/// A command for sending the users messages
/// </summary>
public ICommand SendCommand { get; set; }
/// <summary>
/// ChatPageViewModel Constructor
/// </summary>
public ChatbotPageViewModel()
{
SendCommand = new RelayCommand(Send);
chatbot = new OscovaBot();
var assembly = IntrospectionExtensions.GetTypeInfo(typeof(MainPage)).Assembly;
Stream stream = assembly.GetManifestResourceStream("BluePillApp.Helpers.nice.siml");
chatbot.Import(XDocument.Load(stream));
chatbot.Trainer.StartTraining();
}
/// <summary>
/// This function sends a message
/// </summary>
public void Send()
{
if (!string.IsNullOrEmpty(TextToSend))
{
//This adds a new message to the messages collection
Messages.Add(new ChatMessageModel() { Text = TextToSend, User = App.User });
//This gets the chatbots response for each message
chatbot.MainUser.ResponseReceived += async (sender, args) =>
{
await Task.Delay(1500);
Messages.Add(new ChatMessageModel() { Text = args.Response.Text });
};
var result = chatbot.Evaluate(TextToSend);
result.Invoke();
//Removes the text in the Entry after message is sent
TextToSend = string.Empty;
}
}
}

Related

WPF - Pass data between Viewmodels

I have a view model and field and I want to send field another view model. I cant use object initialize because the constructor executions first and this field is null. I need this field in the constructor. I tried constructor parameter but that didn't work I get a error;
'ProductDetailViewModel' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TVm' in the generic type or method 'BasePage<TVm>'
P.S: sorry for my English :|
My Base Page:
/// <summary>
/// The base page for all pages to gain base functionality
/// </summary>
public class BasePage : UserControl
{
#region Private Member
/// <summary>
/// The View Model associated with this page
/// </summary>
private object mViewModel;
#endregion
#region Public Properties
/// <summary>
/// The animation the play when the page is first loaded
/// </summary>
public PageAnimation PageLoadAnimation { get; set; } = PageAnimation.SlideAndFadeInFromRight;
/// <summary>
/// The animation the play when the page is unloaded
/// </summary>
public PageAnimation PageUnloadAnimation { get; set; } = PageAnimation.SlideAndFadeOutToLeft;
/// <summary>
/// The time any slide animation takes to complete
/// </summary>
public float SlideSeconds { get; set; } = 0.4f;
/// <summary>
/// A flag to indicate if this page should animate out on load.
/// Useful for when we are moving the page to another frame
/// </summary>
public bool ShouldAnimateOut { get; set; }
/// <summary>
/// The View Model associated with this page
/// </summary>
public object ViewModelObject
{
get => mViewModel;
set
{
// If nothing has changed, return
if (mViewModel == value)
return;
// Update the value
mViewModel = value;
// Fire the view model changed method
OnViewModelChanged();
// Set the data context for this page
DataContext = mViewModel;
}
}
#endregion
#region Constructor
/// <summary>
/// Default constructor
/// </summary>
public BasePage()
{
// Don't bother animating in design time
if (DesignerProperties.GetIsInDesignMode(this))
return;
// If we are animating in, hide to begin with
if (PageLoadAnimation != PageAnimation.None)
Visibility = Visibility.Collapsed;
// Listen out for the page loading
Loaded += BasePage_LoadedAsync;
}
#endregion
#region Animation Load / Unload
/// <summary>
/// Once the page is loaded, perform any required animation
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void BasePage_LoadedAsync(object sender, System.Windows.RoutedEventArgs e)
{
// If we are setup to animate out on load
if (ShouldAnimateOut)
// Animate out the page
await AnimateOutAsync();
// Otherwise...
else
// Animate the page in
await AnimateInAsync();
}
/// <summary>
/// Animates the page in
/// </summary>
/// <returns></returns>
public async Task AnimateInAsync()
{
// Make sure we have something to do
if (PageLoadAnimation == PageAnimation.None)
return;
switch (PageLoadAnimation)
{
case PageAnimation.SlideAndFadeInFromRight:
// Start the animation
await this.SlideAndFadeInAsync(AnimationSlideInDirection.Right, false, SlideSeconds,
size: (int)Application.Current.MainWindow.Width);
break;
}
}
/// <summary>
/// Animates the page out
/// </summary>
/// <returns></returns>
public async Task AnimateOutAsync()
{
// Make sure we have something to do
if (PageUnloadAnimation == PageAnimation.None)
return;
switch (PageUnloadAnimation)
{
case PageAnimation.SlideAndFadeOutToLeft:
// Start the animation
await this.SlideAndFadeOutAsync(AnimationSlideInDirection.Left, SlideSeconds);
break;
}
}
#endregion
/// <summary>
/// Fired when the view model changes
/// </summary>
protected virtual void OnViewModelChanged()
{
}
}
/// <summary>
/// A base page with added ViewModel support
/// </summary>
public class BasePage<TVm> : BasePage
where TVm : BaseViewModel, new()
{
#region Public Properties
/// <summary>
/// The view model associated with this page
/// </summary>
public TVm ViewModel
{
get => (TVm)ViewModelObject;
set => ViewModelObject = value;
}
#endregion
#region Constructor
/// <summary>
/// Default constructor
/// </summary>
public BasePage() : base()
{
// If in design time mode...
if (DesignerProperties.GetIsInDesignMode(this))
// Just use a new instance of the VM
ViewModel = new TVm();
else
// Create a default view model
ViewModel = Framework.Service<TVm>() ?? new TVm();
}
/// <summary>
/// Constructor with specific view model
/// </summary>
/// <param name="specificViewModel">The specific view model to use, if any</param>
public BasePage(TVm specificViewModel = null) : base()
{
// Set specific view model
if (specificViewModel != null)
ViewModel = specificViewModel;
else
{
// If in design time mode...
if (DesignerProperties.GetIsInDesignMode(this))
// Just use a new instance of the VM
ViewModel = new TVm();
else
{
// Create a default view model
ViewModel = Framework.Service<TVm>() ?? new TVm();
}
}
}
#endregion
}
My Base View Model:
/// <summary>
/// A base view model that fires Property Changed events as needed
/// </summary>
public class BaseViewModel : INotifyPropertyChanged
{
#region Protected Members
/// <summary>
/// A global lock for property checks so prevent locking on different instances of expressions.
/// Considering how fast this check will always be it isn't an issue to globally lock all callers.
/// </summary>
protected object mPropertyValueCheckLock = new object();
#endregion
/// <summary>
/// The event that is fired when any child property changes its value
/// </summary>
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
/// <summary>
/// Call this to fire a <see cref="PropertyChanged"/> event
/// </summary>
/// <param name="name"></param>
public void OnPropertyChanged(string name)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
#region Command Helpers
/// <summary>
/// Runs a command if the updating flag is not set.
/// If the flag is true (indicating the function is already running) then the action is not run.
/// If the flag is false (indicating no running function) then the action is run.
/// Once the action is finished if it was run, then the flag is reset to false
/// </summary>
/// <param name="updatingFlag">The boolean property flag defining if the command is already running</param>
/// <param name="action">The action to run if the command is not already running</param>
/// <returns></returns>
protected async Task RunCommandAsync(Expression<Func<bool>> updatingFlag, Func<Task> action)
{
// Lock to ensure single access to check
lock (mPropertyValueCheckLock)
{
// Check if the flag property is true (meaning the function is already running)
if (updatingFlag.GetPropertyValue())
return;
// Set the property flag to true to indicate we are running
updatingFlag.SetPropertyValue(true);
}
try
{
// Run the passed in action
await action();
}
finally
{
// Set the property flag back to false now it's finished
updatingFlag.SetPropertyValue(false);
}
}
/// <summary>
/// Runs a command if the updating flag is not set.
/// If the flag is true (indicating the function is already running) then the action is not run.
/// If the flag is false (indicating no running function) then the action is run.
/// Once the action is finished if it was run, then the flag is reset to false
/// </summary>
/// <param name="updatingFlag">The boolean property flag defining if the command is already running</param>
/// <param name="action">The action to run if the command is not already running</param>
/// <typeparam name="T">The type the action returns</typeparam>
/// <returns></returns>
protected async Task<T> RunCommandAsync<T>(Expression<Func<bool>> updatingFlag, Func<Task<T>> action, T defaultValue = default(T))
{
// Lock to ensure single access to check
lock (mPropertyValueCheckLock)
{
// Check if the flag property is true (meaning the function is already running)
if (updatingFlag.GetPropertyValue())
return defaultValue;
// Set the property flag to true to indicate we are running
updatingFlag.SetPropertyValue(true);
}
try
{
// Run the passed in action
return await action();
}
finally
{
// Set the property flag back to false now it's finished
updatingFlag.SetPropertyValue(false);
}
}
#endregion
}
View Model Constructor:
View Model

System.Windows.Markup.XamlParseException For Kinect

Im currently facing a weird problem with the Microsoft Kinect SDK 2.0 Browser Sample code of Discrete Gesture Basics-WPF
Objective: is to add my own recording of the gesture in filename.gbd into the database and able to extract the gesture inside.
Problem: When i changed the initial name from 'Seated.gbd' to 'RightHandHandsUp.gbd' The error occured below.
However, if i were to rename my 'RightHandHandsUp.gbd' file to 'Seated.gbd' ( the default), it would work. Which is weird, but i suspect there is some binding somewhere in the program but i have already tried searching the entire project for the keyword.
Thanks in advance!!
Error Message:
System.Windows.Markup.XamlParseException: ''The invocation of the constructor on type 'Microsoft.Samples.Kinect.DiscreteGestureBasics.MainWindow' that matches the specified binding constraints threw an exception.' Line number '4' and line position '9'.'
With 2 inner exceptions:
InvalidOperationException: This API has returned an exception from an HRESULT: 0x80004005
And
COMException: Error HRESULT E_FAIL has been returned from a call to a COM component.
so in the GestureDetector.cs file where i changed the name of the file.
//------------------------------------------------------------------------------
// <copyright file="GestureDetector.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace Microsoft.Samples.Kinect.DiscreteGestureBasics
{
using System;
using System.Collections.Generic;
using Microsoft.Kinect;
using Microsoft.Kinect.VisualGestureBuilder;
/// <summary>
/// Gesture Detector class which listens for VisualGestureBuilderFrame events from the service
/// and updates the associated GestureResultView object with the latest results for the 'Seated' gesture
/// </summary>
public class GestureDetector : IDisposable
{
/// <summary> Path to the gesture database that was trained with VGB </summary>
private readonly string gestureDatabase = #"Database\RightHandHandsUp.gbd";
/// <summary> Name of the discrete gesture in the database that we want to track </summary>
private readonly string seatedGestureName = "HandsUp_Right";
/// <summary> Gesture frame source which should be tied to a body tracking ID </summary>
private VisualGestureBuilderFrameSource vgbFrameSource = null;
/// <summary> Gesture frame reader which will handle gesture events coming from the sensor </summary>
private VisualGestureBuilderFrameReader vgbFrameReader = null;
/// <summary>
/// Initializes a new instance of the GestureDetector class along with the gesture frame source and reader
/// </summary>
/// <param name="kinectSensor">Active sensor to initialize the VisualGestureBuilderFrameSource object with</param>
/// <param name="gestureResultView">GestureResultView object to store gesture results of a single body to</param>
public GestureDetector(KinectSensor kinectSensor, GestureResultView gestureResultView)
{
if (kinectSensor == null)
{
throw new ArgumentNullException("kinectSensor");
}
if (gestureResultView == null)
{
throw new ArgumentNullException("gestureResultView");
}
this.GestureResultView = gestureResultView;
// create the vgb source. The associated body tracking ID will be set when a valid body frame arrives from the sensor.
this.vgbFrameSource = new VisualGestureBuilderFrameSource(kinectSensor, 0);
this.vgbFrameSource.TrackingIdLost += this.Source_TrackingIdLost;
// open the reader for the vgb frames
this.vgbFrameReader = this.vgbFrameSource.OpenReader();
if (this.vgbFrameReader != null)
{
this.vgbFrameReader.IsPaused = true;
this.vgbFrameReader.FrameArrived += this.Reader_GestureFrameArrived;
}
// load the 'Seated' gesture from the gesture database
using (VisualGestureBuilderDatabase database = new VisualGestureBuilderDatabase(this.gestureDatabase))
{
// we could load all available gestures in the database with a call to vgbFrameSource.AddGestures(database.AvailableGestures),
// but for this program, we only want to track one discrete gesture from the database, so we'll load it by name
foreach (Gesture gesture in database.AvailableGestures)
{
if (gesture.Name.Equals(this.seatedGestureName))
{
this.vgbFrameSource.AddGesture(gesture);
}
}
}
}
/// <summary> Gets the GestureResultView object which stores the detector results for display in the UI </summary>
public GestureResultView GestureResultView { get; private set; }
/// <summary>
/// Gets or sets the body tracking ID associated with the current detector
/// The tracking ID can change whenever a body comes in/out of scope
/// </summary>
public ulong TrackingId
{
get
{
return this.vgbFrameSource.TrackingId;
}
set
{
if (this.vgbFrameSource.TrackingId != value)
{
this.vgbFrameSource.TrackingId = value;
}
}
}
/// <summary>
/// Gets or sets a value indicating whether or not the detector is currently paused
/// If the body tracking ID associated with the detector is not valid, then the detector should be paused
/// </summary>
public bool IsPaused
{
get
{
return this.vgbFrameReader.IsPaused;
}
set
{
if (this.vgbFrameReader.IsPaused != value)
{
this.vgbFrameReader.IsPaused = value;
}
}
}
/// <summary>
/// Disposes all unmanaged resources for the class
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Disposes the VisualGestureBuilderFrameSource and VisualGestureBuilderFrameReader objects
/// </summary>
/// <param name="disposing">True if Dispose was called directly, false if the GC handles the disposing</param>
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
if (this.vgbFrameReader != null)
{
this.vgbFrameReader.FrameArrived -= this.Reader_GestureFrameArrived;
this.vgbFrameReader.Dispose();
this.vgbFrameReader = null;
}
if (this.vgbFrameSource != null)
{
this.vgbFrameSource.TrackingIdLost -= this.Source_TrackingIdLost;
this.vgbFrameSource.Dispose();
this.vgbFrameSource = null;
}
}
}
/// <summary>
/// Handles gesture detection results arriving from the sensor for the associated body tracking Id
/// </summary>
/// <param name="sender">object sending the event</param>
/// <param name="e">event arguments</param>
private void Reader_GestureFrameArrived(object sender, VisualGestureBuilderFrameArrivedEventArgs e)
{
VisualGestureBuilderFrameReference frameReference = e.FrameReference;
using (VisualGestureBuilderFrame frame = frameReference.AcquireFrame())
{
if (frame != null)
{
// get the discrete gesture results which arrived with the latest frame
IReadOnlyDictionary<Gesture, DiscreteGestureResult> discreteResults = frame.DiscreteGestureResults;
if (discreteResults != null)
{
// we only have one gesture in this source object, but you can get multiple gestures
foreach (Gesture gesture in this.vgbFrameSource.Gestures)
{
if (gesture.Name.Equals(this.seatedGestureName) && gesture.GestureType == GestureType.Discrete)
{
DiscreteGestureResult result = null;
discreteResults.TryGetValue(gesture, out result);
if (result != null)
{
// update the GestureResultView object with new gesture result values
this.GestureResultView.UpdateGestureResult(true, result.Detected, result.Confidence);
}
}
}
}
}
}
}
/// <summary>
/// Handles the TrackingIdLost event for the VisualGestureBuilderSource object
/// </summary>
/// <param name="sender">object sending the event</param>
/// <param name="e">event arguments</param>
private void Source_TrackingIdLost(object sender, TrackingIdLostEventArgs e)
{
// update the GestureResultView object to show the 'Not Tracked' image in the UI
this.GestureResultView.UpdateGestureResult(false, false, 0.0f);
}
}
}
In my case I didn't know that DiscreteGestureBasics is using AdaBoostTech.dll and RFRProgressTech.dll. I copied directory vgtech from DiscreteGestureBuilder example bin directory to my other project bin directory and everything works fine.

Unity: Singleton DontDestroyOnLoad Build Error

I am using XMGUnityLib Plugin. The program runs fine in the editor without any error but when I try to build it (Android) then while at pushing apk step it gives this error:
InvalidOperationException: The following game object is invoking the DontDestroyOnLoad method: AnalyticsManager. Notice that DontDestroyOnLoad can only be used in play mode and, as such, cannot be part of an editor script.
XMGSingleton`1[XMGAnalyticsManager].get_Instance () (at Assets/XMG/UnityLib/Util/XMGSingleton.cs:28)
AndroidStorePostProcess.OnPostprocessScene () (at Assets/Editor/AndroidStorePostProcess.cs:32)
UnityEditor.HostView:OnGUI()
XMGSingleton.cs:
using UnityEngine;
using System.Collections;
using System;
/// <summary>
/// XMG singleton base Class.
/// Inherit your Manager Monobehavior classes that you want to be Singletons
/// </summary>
public abstract class XMGSingleton<T> : MonoBehaviour where T : MonoBehaviour
{
protected static T instance;
/// <summary>
/// Returns the instance of this singleton
/// Make sure we don't get destroyed on Scene Load
/// </summary>
public static T Instance {
get {
if(instance == null) {
instance = (T) FindObjectOfType(typeof(T));
if (instance == null) {
Debug.LogError("An instance of " + typeof(T) +
" is needed in the scene, but there is none.");
return null;
}
DontDestroyOnLoad(instance.gameObject);
}
return instance;
}
}
#region Public Methods
public static bool HasInstance {
get {
if( (T) FindObjectOfType(typeof(T)) != null ) {
return true;
} else {
return false;
}
}
}
/// <summary>
/// Helper to cast the Instance to the Type provided
/// </summary>
public static Type MyInstance<Type> () {
return (Type)(object)Instance;
}
#endregion
}
XMGAnalyticsManager.cs:
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using Com.XMG.UnityLib.Community.OnlineGame;
using Com.XMG.UnityLib.Tracking;
#if UNITY_IPHONE
using FlurryAccess = FlurryBinding;
#elif UNITY_ANDROID
using FlurryAccess = FlurryAndroid;
#endif
public abstract partial class XMGAnalyticsManager : XMGSingleton<XMGAnalyticsManager> {
#region Private Fields
/// <summary>
/// The type of the current build.
/// For Analytics QA testing point to Analytics
/// For QA Testing point to SandBox
/// For Production/Release build point to Production
/// </summary>
[SerializeField]
private BuildTypeEnum buildType = BuildTypeEnum.SandBox;
/// <summary>
/// The type of the android store type
/// </summary>
[SerializeField]
private AndroidStoreTypeEnum androidStoreType = AndroidStoreTypeEnum.Google;
/// <summary>
/// The swrve authentication Info
/// </summary>
[SerializeField]
private SwrveAuthentication swrveAuth;
/// <summary>
/// The flurry auth.
/// </summary>
[SerializeField]
private FlurryAuthentication flurryAuth;
/// <summary>
/// Sends events every X seconds.
/// </summary>
[SerializeField]
private float sendEventsEverySeconds = 30;
/// <summary>
/// The parent screen.
/// </summary>
[SerializeField]
private string parentScreen = "SplashPage";
/// <summary>
/// The grand parent screen.
/// </summary>
string grandParentScreen = "null";
/// <summary>
/// My external IP.
/// </summary>
string myExtIP = "";
/// <summary>
/// The swrve component.
/// </summary>
private SwrveComponent swrveComponent;
private Dictionary<string, JSONObject> swrveResources = new Dictionary<string, JSONObject>();
/// <summary>
/// Resource hashmap ----> (key: resource name, value: json object)
/// </summary>
public Dictionary<string, JSONObject> Resources {
get {
return this.swrveResources;
}
}
/// <summary>
/// This event is triggered when swrve data is updated.
/// </summary>
public static event Action ItemsConfigurationUpdatedEvent = delegate {};
/// <summary>
/// True if we are in the fetching of AB Test Data State
/// </summary>
private bool isFetchingABParams = false;
/// <summary>
/// The current swrve identifier.
/// </summary>
private string currentSwrveId = null;
/// <summary>
/// The android bundle version.
/// </summary>
[SerializeField]
protected string androidBundleVersion = "1.0";
#endregion
#region Public Properties
/// <summary>
/// True if it's a production build
/// </summary>
public bool IsProductionBuild {
get { return buildType == BuildTypeEnum.Production; }
}
/// <summary>
/// Gets the type of the android store.
/// </summary>
public AndroidStoreTypeEnum AndroidStoreType {
get { return androidStoreType; }
set { androidStoreType = value; }
}
/// <summary>
/// True if its a google play store build
/// </summary>
public bool IsGoogleStoreType {
get { return androidStoreType == AndroidStoreTypeEnum.Google; }
}
/// <summary>
/// True if its an Amazon store build
/// </summary>
public bool IsAmazonStoreType {
get { return androidStoreType == AndroidStoreTypeEnum.Amazon; }
}
#endregion
#region Monobehaviors & Init
/// <summary>
/// Add event Listeners
/// </summary>
void Awake() {
XMGOnlineProfileManager.OnProfileLoadedEvent += ProfileHasLoadedCallback;
XMGCommunityManager.OnCreateRealProfileOnServerEvent += ProfileCreatedOnServerCallback;
XMGCommunityManager.OnNewFriendsAddedEvent += NewFriendsAddedCallback;
RegisterGameListeners();
// TODO: Listen for Profile Change events
swrveAuth.Init();
flurryAuth.Init();
}
/// <summary>
/// Starts the SWRVE Event timer
/// </summary>
void Start() {
StartSwrveTimer();
StartCoroutine( CheckIP() );
}
/// <summary>
/// Checks for the IP
/// </summary>
/// <returns>
/// The IP
/// </returns>
IEnumerator CheckIP(){
WWW myExtIPWWW = new WWW("http://checkip.dyndns.org");
if( myExtIPWWW != null ) {
yield return myExtIPWWW;
if( myExtIPWWW.error == null ) {
myExtIP=myExtIPWWW.text;
myExtIP=myExtIP.Substring(myExtIP.IndexOf(":")+1);
myExtIP=myExtIP.Substring(0,myExtIP.IndexOf("<"));
}
}
}
/// <summary>
/// Detach event Listeners
/// </summary>
void OnDestroy() {
XMGOnlineProfileManager.OnProfileLoadedEvent -= ProfileHasLoadedCallback;
XMGCommunityManager.OnCreateRealProfileOnServerEvent -= ProfileCreatedOnServerCallback;
XMGCommunityManager.OnNewFriendsAddedEvent -= NewFriendsAddedCallback;
UnRegisterGameListeners();
}
/// <summary>
/// If Pause end session
/// If Resume Start session and Re-Login.
/// At any case post events
/// </summary>
/// <param name='pause'>
/// Pause.
/// </param>
void OnApplicationPause(bool pause) {
if (currentSwrveId != null) {
if(pause) {
swrveComponent.Swrve_AddSessionEnd();
} else {
swrveComponent.Swrve_AddSessionStart();
SessionStart();
AnalyticsLogout();
AnalyticsLogin();
}
LogUserProperties();
swrveComponent.Swrve_PostEvents(currentSwrveId, SWRVE_APP_VERSION);
}
}
/// <summary>
/// Init plugins.The Profile Has loaded
/// </summary>
void ProfileHasLoadedCallback() {
// Get Swrve component
swrveComponent = gameObject.GetComponentInChildren<SwrveComponent>();
AnalyticsLogin();
// Check if we've set the random user property, if not then set it
if (!PlayerPrefs.HasKey(RANDOM_TEST_PROPERTY_KEY)) {
PlayerPrefs.SetString(RANDOM_TEST_PROPERTY_KEY, "true");
JSONObject userProperty = new JSONObject(JSONObject.Type.OBJECT);
int random = NGUITools.RandomRange(1, 100);
userProperty.AddField("RandNum", random.ToString());
swrveComponent.Swrve_AddUserUpdateEvent(userProperty.ToString());
}
}
/// <summary>
/// A new Online Profile created callback.
/// </summary>
void ProfileCreatedOnServerCallback() {
AddNamedEvent( "Community.FacebookUserCreated" );
}
/// <summary>
/// New friends were added callback.
/// </summary>
/// <param name='newFriendsAdded'>
/// The New friends added
/// </param>
void NewFriendsAddedCallback( int newFriendsAdded ) {
JSONObject jTournamentFriends = new JSONObject(JSONObject.Type.OBJECT);
jTournamentFriends.AddField( "NewFriendsAdded", BucketGeneral( newFriendsAdded ) );
AddNamedEvent ( "Community.TournamentAddedFriend", jTournamentFriends );
}
#endregion
#region Abstract & Virtual Methods
/// <summary>
/// Gets the tracking Object
/// </summary>
protected abstract XMGTracking GetTracking();
/// <summary>
/// Registers the game listeners.
/// </summary>
protected abstract void RegisterGameListeners();
/// <summary>
/// Unregister the game listeners.
/// </summary>
protected abstract void UnRegisterGameListeners();
/// <summary>
/// Logs the game user properties.
/// </summary>
/// <param name='jUserProperties'>
/// The JSON Object with the user properties.
/// </param>
protected abstract void LogGameUserProperties(JSONObject jUserProperties);
/// <summary>
/// A session Is Started
/// </summary>
protected abstract void SessionStart();
/// <summary>
/// The session has ended
/// </summary>
protected abstract void SessionEnd();
/// <summary>
/// Gets the user start time.
/// Default to Now
/// Override to provide the user's start time
/// </summary>
/// <returns>
/// The user start time.
/// </returns>
protected virtual long GetUserStartTime() {
return (long)XMGUnityUtil.ToUnixTimeFromUniversal( DateTime.UtcNow );
}
#endregion
#region Private Methods
/// <summary>
/// Starts the swrve timer.
/// </summary>
private void StartSwrveTimer() {
StartCoroutine( StartEventsTimer());
}
/// <summary>
/// Logs the user properties.
/// And Calls abstract LogGameUserProperties
/// </summary>
private void LogUserProperties() {
JSONObject jUserProperties = new JSONObject(JSONObject.Type.OBJECT);
// Add here Common User Properties
XMGTracking tracking = GetTracking();
jUserProperties.AddField( UP_FIRST_LAUNCH_DAY, tracking.FirstLaunch.Day );
jUserProperties.AddField( UP_FIRST_LAUNCH_MONTH, tracking.FirstLaunch.Month );
jUserProperties.AddField( UP_FIRST_LAUNCH_YEAR, tracking.FirstLaunch.Year );
jUserProperties.AddField( UP_FIRST_LAUNCH_EPOCH, XMGUnityUtil.ToUnixTimeFromUniversal( tracking.FirstLaunch ).ToString() );
jUserProperties.AddField( UP_TOTAL_TAPJOY_REWARDS, tracking.TotalTapJoyPointsEarned );
jUserProperties.AddField( UP_TOTAL_SPONSORPAY_REWARDS, tracking.TotalSponsorPayPointsEarned );
jUserProperties.AddField( UP_TOTAL_CHARTBOOST_ADS_CLICKED, tracking.TotalChartBoostAdsClicked );
jUserProperties.AddField( UP_TOTAL_CHARTBOOST_ADS_CLOSED, tracking.TotalChartBoostAdsClosed );
jUserProperties.AddField( UP_TOTAL_SOFT_CURRENCY_EARNED, tracking.SoftCurrencyEarned );
jUserProperties.AddField( UP_TOTAL_HARD_CURRENCY_EARNED, tracking.HardCurrencyEarned );
jUserProperties.AddField( UP_TOTAL_SOFT_CURRENCY_SPENT, tracking.SoftCurrencySpent );
jUserProperties.AddField( UP_TOTAL_HARD_CURRENCY_SPENT, tracking.HardCurrencySpent );
#if UNITY_IPHONE
jUserProperties.AddField( UP_VERION, EtceteraTwoBinding.getInfoPlistValue("CFBundleVersion") );
#elif UNITY_ANDROID
jUserProperties.AddField( UP_VERION, androidBundleVersion );
#endif
jUserProperties.AddField( UP_TOTAL_FRIENDS, tracking.TotalFriends );
jUserProperties.AddField( UP_TOTAL_UNIQUE_DAYS_PLAYED, tracking.TotalUniqueDaysPlayed );
if( string.IsNullOrEmpty( myExtIP ) ) {
jUserProperties.AddField( UP_IP, myExtIP );
}
LogGameUserProperties(jUserProperties);
swrveComponent.Swrve_AddUserUpdateEvent(jUserProperties.ToString());
}
#endregion
#region Authentication
/// <summary>
/// Logs in to Flurry and SWRVE
/// </summary>
void AnalyticsLogin() {
FlurryLogin();
if( currentSwrveId != null ) {
if( !currentSwrveId.Equals( XMGOnlineProfileManager.Instance.ActiveOnlineProfile.GameStats.SwrveId ) ) {
Debug.Log("There is already a swrve account signed in!!! You must close the session first!");
AnalyticsLogout();
} else {
// This swrve user is already logged in, bail out of this function
Debug.Log("Attempt to log into swrve twice with the same id... Ignoring.");
return;
}
}
SwrveLogin(XMGOnlineProfileManager.Instance.ActiveOnlineProfile.GameStats.SwrveId);
}
/// <summary>
/// Logout from Flurry and SWRVE
/// </summary>
private void AnalyticsLogout() {
FlurryLogout();
if(currentSwrveId == null) {
Debug.LogError("There is no active swrve user!!! Cannot close session.");
return;
}
SwrveLogout();
}
/// <summary>
/// Starts Flurry Session
/// </summary>
private void FlurryLogin() {
#if UNITY_IPHONE
FlurryAccess.startSession(flurryAuth.FlurryKey);
#elif UNITY_ANDROID
FlurryAccess.onStartSession(flurryAuth.FlurryKey, true, true );
#endif
}
/// <summary>
/// Logouts from Flurry
/// </summary>
private void FlurryLogout() {
#if UNITY_ANDROID
FlurryAccess.onEndSession();
#endif
}
/// <summary>
/// Login to Swrve
/// </summary>
/// <param name='swrveId'>
/// The Swrve identifier.
/// </param>
private void SwrveLogin(string swrveId) {
currentSwrveId = swrveId;
int gameId = 0;
string apiKey = "";
if (buildType == BuildTypeEnum.Analytics) {
gameId = swrveAuth.AnalyticsGameID;
apiKey = swrveAuth.AnalyticsAPIKey;
} else if (buildType == BuildTypeEnum.SandBox) {
gameId = swrveAuth.SandboxGameID;
apiKey = swrveAuth.SandboxAPIKey;
} else if( buildType == BuildTypeEnum.Production ) {
gameId = swrveAuth.ProductionGameID;
apiKey = swrveAuth.ProductionAPIKey;
}
InitializeSWRVEComponent(gameId,
apiKey,
swrveAuth.PersonalKey,
swrveAuth.URLABTest
);
}
/// <summary>
/// Logout from Swrve
/// </summary>
private void SwrveLogout() {
// Make sure to close the Swrve session
SessionEnd();
swrveComponent.Swrve_AddSessionEnd();
swrveComponent.Swrve_PostEvents(currentSwrveId, SWRVE_APP_VERSION);
currentSwrveId = null;
}
/// <summary>
/// Initializes the SWRVE component.
/// </summary>
/// <param name='gameId'>
/// The Game identifier.
/// </param>
/// <param name='apiKey'>
/// The API key.
/// </param>
/// <param name='personalKey'>
/// Personal key.
/// </param>
/// <param name='abURL'>
/// The Ab Test server URL.
/// </param>
private void InitializeSWRVEComponent(int gameId, string apiKey, string personalKey, string abURL) {
swrveComponent.gameId = gameId;
swrveComponent.apiKey = apiKey;
swrveComponent.abTestServer = abURL;
// Setup swrve for new user
swrveComponent.Swrve_AddSessionStart();
SessionStart();
if(isFetchingABParams == false) {
isFetchingABParams = true;
StartCoroutine(GetAllResourcesWithABTests());
}
}
#endregion
#region A/B Helpers
/// <summary>
/// Returns the most recent JSON configuration for a resource from swrve or NULL if there is none.
/// This means it could return cached data that is old. If you want to retrieve the latest from swrve.
/// </summary>
/// <param name="itemId">
/// A <see cref="System.String"/>
/// </param>
/// <returns>
/// A <see cref="JSONObject"/>
/// </returns>
public JSONObject GetItemParams(string itemId) {
JSONObject item = null;
swrveResources.TryGetValue(itemId, out item);
return item;
}
/// <summary>
/// Gets all resources with AB tests.
/// </summary>
/// <returns>
/// The all resources with AB tests.
/// </returns>
private IEnumerator GetAllResourcesWithABTests() {
string swrveURLRequest = string.Format(swrveAuth.URLABTestResources + "?api_key={0}&user={1}&joined={2}", swrveComponent.apiKey, currentSwrveId, GetUserStartTime() );
WWW itemRequest = new WWW(swrveURLRequest);
yield return itemRequest; // Yield until a result is returned.
if(itemRequest.error != null) {
Debug.LogWarning("Error attempting to fetch Swrve A/B Resource data: " + itemRequest.error);
LoadCachedConfig();
// Bail!
yield break;
}
// Process all the resources into a hashmap ( key: resource name, value: json object )
JSONObject jResources = new JSONObject(itemRequest.text);
if(jResources == null || jResources.type == JSONObject.Type.NULL) {
// Bad data from swrve, abort!
Debug.LogError("Bad A/B resource data from swrve!");
LoadCachedConfig();
yield break;
} else {
//Debug.LogWarning( "Data from SWRVE: " + jResources );
}
XMGSaveLoadUtils.Instance.SaveEncryptedField(CACHED_CONFIG_KEY, itemRequest.text); // Replace the old cached config.
ParseResources(jResources);
if( swrveResources.Count > 0 ) {
ItemsConfigurationUpdatedEvent();
}
isFetchingABParams = false;
}
/// <summary>
/// Loads the cached swrve config file
/// </summary>
private void LoadCachedConfig() {
string cached = XMGSaveLoadUtils.Instance.LoadEncryptedField(CACHED_CONFIG_KEY);
if(string.IsNullOrEmpty(cached)) {
// There was no cached data
return;
}
// Apply the cached resources so it doesn't block updates.
JSONObject jResources = new JSONObject(cached);
ParseResources(jResources);
if( swrveResources.Count > 0 ) {
ItemsConfigurationUpdatedEvent();
}
}
/// <summary>
/// Expects a JSON array of resources.
/// </summary>
/// <param name="resources">
/// A <see cref="JSONObject"/>
/// </param>
private void ParseResources(JSONObject jResources) {
swrveResources.Clear();
//Debug.Log( "Parsing SWRVE Resources: " + jResources );
if(jResources == null || jResources.type != JSONObject.Type.ARRAY) {
// Bad data from swrve, abort!
Debug.LogError("Could not parse resource data, unexpected format!");
return;
}
foreach(JSONObject data in jResources.list) {
JSONObject key = data.GetField("uid");
if(key == null || key.type != JSONObject.Type.STRING || string.IsNullOrEmpty( JSONObject.GetString(key) ) ) {
// Bad item, on to the next
Debug.LogWarning("Bad item, no property 'uid' in " + data.print());
continue;
}
// Add the resource over top of any precached configuration
swrveResources[JSONObject.GetString(key)] = data;
}
}
#endregion
#region Event Handling
/// <summary>
/// Adds the named event.
/// </summary>
/// <param name='name'>
/// The EventName.
/// </param>
public void AddNamedEvent(string name) {
swrveComponent.Swrve_AddNamedEvent(name, "{}");
#if UNITY_IPHONE
FlurryAccess.logEvent(name, false);
#elif UNITY_ANDROID
FlurryAccess.logEvent(name);
#endif
}
/// <summary>
/// Adds a named event with Payload
/// </summary>
/// <param name='name'>
/// The EventName.
/// </param>
/// <param name='payload'>
/// Payload.
/// </param>
public void AddNamedEvent(string name, JSONObject payload) {
string data = payload.ToString();
if(!string.IsNullOrEmpty(data)) {
swrveComponent.Swrve_AddNamedEvent(name, data);
} else {
swrveComponent.Swrve_AddNamedEvent(name, "{}");
}
#if UNITY_IPHONE
FlurryAccess.logEventWithParameters(name, payload.ToDictionary(), false);
#elif UNITY_ANDROID
FlurryAccess.logEvent(name, payload.ToDictionary());
#endif
}
/// <summary>
/// Infinite Loop to send events in the queue
/// </summary>
private IEnumerator StartEventsTimer() {
float timeInterval = Time.realtimeSinceStartup;
while (true) {
if(Time.realtimeSinceStartup - timeInterval >= sendEventsEverySeconds) {
SendEvents();
timeInterval = Time.realtimeSinceStartup;
}
yield return new WaitForSeconds( 1 );
}
}
/// <summary>
/// Sends the events to SWRVE
/// </summary>
private void SendEvents() {
if(currentSwrveId != null) {
swrveComponent.Swrve_PostEvents(currentSwrveId, SWRVE_APP_VERSION);
}
}
#endregion
#region UI Events
/// <summary>
/// Logs a UI screen visit event.
/// Records the current screen, the parent screen, and the grandparent screen.
/// </summary>
/// <param name='screenName'>
/// Screen name.
/// </param>
public void ScreenVisitEvent(string screenName) {
JSONObject eventParams = new JSONObject(JSONObject.Type.OBJECT);
if(parentScreen != null) {
eventParams.AddField("parent", parentScreen);
}
if(grandParentScreen != null) {
eventParams.AddField("grandParent", grandParentScreen);
}
AddNamedEvent("UI.Flow." + screenName, eventParams);
eventParams.AddField("screen", screenName);
AddNamedEvent("UI.Flow.Screens", eventParams);
grandParentScreen = parentScreen;
parentScreen = screenName;
}
/// <summary>
/// Logs a UI button pressed event
/// </summary>
/// <param name='buttonName'>
/// The Button name.
/// </param>
public void ButtonPressedEvent( string buttonName ) {
AddNamedEvent("UI.ButtonPressed." + buttonName );
}
#endregion
#region Purchase Events
/// <summary>
/// Sends an analytics buy in event for purchasing inapps.
/// </summary>
/// <param name='rewardCurrency'>
/// Reward currency
/// </param>
/// <param name='rewardAmount'>
/// Amount of rewardCurrency
/// </param>
/// <param name='localCost'>
/// The real money price in local currency ( e.g 0.99 )
/// </param>
/// <param name='localCurrency'>
/// CAD, USD, etc...
/// </param>
public void AddBuyInEvent(string rewardCurrency, int rewardAmount, float localCost, string localCurrency, string itemID ) {
swrveComponent.Swrve_AddBuyInEvent( "", rewardCurrency, rewardAmount, Mathf.Round(localCost* 100)/100, localCurrency );
AddNamedEvent( "Purhases.IAP" );
AddNamedEvent( "Purhases.IAP.HardCurrency" + itemID );
}
/// <summary>
/// Analytics event when a user purchases an item in-game.
/// </summary>
/// <param name='itemID'>
/// The UID for the Item
/// </param>
/// <param name='currency'>
/// The currency
/// </param>
/// <param name='itemCost'>
/// Item cost.
/// </param>
/// <param name='itemQuantity'>
/// Item quantity.
/// </param>
public void AddPurchaseEvent( string itemID, string currency, int itemCost, int itemQuantity ) {
swrveComponent.Swrve_AddPurchaseItemEvent( itemID, currency, itemCost, itemQuantity );
}
/// <summary>
/// Adds a purchase conversion event.
/// </summary>
/// <param name='itemID'>
///The unique Item ID
/// </param>
public void AddPurchaseConversionPackEvent( string itemID ) {
AddNamedEvent( "Purchases.HardCurrency.SoftCurrency" );
AddNamedEvent( "Purhases.HardCurrency.SoftCurrency." + itemID );
}
#endregion
}
Is this singleton class correctly coded? If yes, what could be the problem?
Any help will be appreciated.
Thanks :)
Do you have an "Editor Script" that inherits from this class? If so then most probably this is what's causing the error. If you're not sure what this means, try looking for a folder in your project named Editor and looking in its scripts. Or try searching for any scripts that are using UnityEditor; and that inherit from Editor like this: public class Name : Editor and make them not inherit from this class.
Another solution is try commenting the DontDestroyOnLoad line (line 26 in XMGSingleton.cs) and put it yourself where you need it.
Go to the objects that you're using this script with and do something like this:
void Awake()
{
if (instance == null)
{
DontDestroyOnLoad(gameObject);
instance = this;
}
else
if (instance != this)
Destroy(gameObject);
}
Though the post is a bit old, I recently bumped into this issue. It was a bit confusing because I had not added any new editor script and other editor scripts were not being called from the Singleton GameObject that the error was pointing to.
When I tried restarting Unity and then again created the build, it started giving some other exception.
However, I finally found the issue was being caused by a method from another object (created by the Singleton's OnAwake method) trying to access an uninitialized property of the Singleton (because the OnAwake method execution was not completed).
I fixed it by delaying the other object's method callback based on a different event.

Changing the property of a xaml button from another xaml

I'm new to Silverlight and MVVM and I'm trying to change Button.IsEnabled of one xaml from another xaml inside the app.
I tried to create a class but I'm not sure I'm using it correctly...
using System.ComponentModel;
namespace Reporting.Models
{
public class DataSourceValidation : INotifyPropertyChanged
{
private bool _isSaveEnabled;
public bool IsSaveEnabled
{
get
{
return _isSaveEnabled;
}
set
{
_isSaveEnabled = value;
RaisedPropertyChanged("IsSaveEnabled");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisedPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
I'd be very grateful for an explanation and example. I've been looking but its not quite clicking with me on how you bind the control to a model and then change that value from another xaml.cs... Provided I am even thinking about this the correct way.
If I need to provide more information just ask. Thanks for your time.
If you just wan't to get it done quickly (no good solution) then you can make this happen using static properties:
In ExampleA.xaml.cs create the following property:
public static ExampleA CurrentInstance { get; private set; }
and in the constructor of ExampleA (I will assume that the name of the class is ExampleA):
public ExampleA()
{
this.InitializeComponent();
CurrentInstance = this;
}
Then in your ExampleB class LostFocus method:
if (ExampleA.CurrentInstance != null)
{
var button = ExampleA.CurrentInstance.FindName("OKButton") as Button;
if (button != null)
{
button.IsEnabled = true;
}
}
On the other hand, I don't know how your classes are structured and how these XAML files are related, but a good universal solution would be by using the Event Aggregator pattern:
I am using here a slightly modified version of the EventAggregator class from the Caliburn.Micro MVVM framework:
namespace Caliburn.Micro
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
/// <summary>
/// A marker interface for classes that subscribe to messages.
/// </summary>
public interface IHandle { }
/// <summary>
/// Denotes a class which can handle a particular type of message.
/// </summary>
/// <typeparam name = "TMessage">The type of message to handle.</typeparam>
public interface IHandle<TMessage> : IHandle
{
/// <summary>
/// Handles the message.
/// </summary>
/// <param name = "message">The message.</param>
void Handle(TMessage message);
}
/// <summary>
/// Enables loosely-coupled publication of and subscription to events.
/// </summary>
public interface IEventAggregator
{
/// <summary>
/// Gets or sets the default publication thread marshaller.
/// </summary>
/// <value>
/// The default publication thread marshaller.
/// </value>
Action<System.Action> PublicationThreadMarshaller { get; set; }
/// <summary>
/// Subscribes an instance to all events declared through implementations of <see cref = "IHandle{T}" />
/// </summary>
/// <param name = "instance">The instance to subscribe for event publication.</param>
void Subscribe(object instance);
/// <summary>
/// Unsubscribes the instance from all events.
/// </summary>
/// <param name = "instance">The instance to unsubscribe.</param>
void Unsubscribe(object instance);
/// <summary>
/// Publishes a message.
/// </summary>
/// <param name = "message">The message instance.</param>
/// <remarks>
/// Uses the default thread marshaller during publication.
/// </remarks>
void Publish(object message);
/// <summary>
/// Publishes a message.
/// </summary>
/// <param name = "message">The message instance.</param>
/// <param name = "marshal">Allows the publisher to provide a custom thread marshaller for the message publication.</param>
void Publish(object message, Action<System.Action> marshal);
}
/// <summary>
/// Enables loosely-coupled publication of and subscription to events.
/// </summary>
public class EventAggregator : IEventAggregator
{
/// <summary>
/// The singleton instance.
/// </summary>
public static EventAggregator Instance = new EventAggregator();
/// <summary>
/// The default thread marshaller used for publication;
/// </summary>
public static Action<System.Action> DefaultPublicationThreadMarshaller = action => action();
readonly List<Handler> handlers = new List<Handler>();
/// <summary>
/// Initializes a new instance of the <see cref = "EventAggregator" /> class.
/// </summary>
public EventAggregator()
{
PublicationThreadMarshaller = DefaultPublicationThreadMarshaller;
}
/// <summary>
/// Gets or sets the default publication thread marshaller.
/// </summary>
/// <value>
/// The default publication thread marshaller.
/// </value>
public Action<System.Action> PublicationThreadMarshaller { get; set; }
/// <summary>
/// Subscribes an instance to all events declared through implementations of <see cref = "IHandle{T}" />
/// </summary>
/// <param name = "instance">The instance to subscribe for event publication.</param>
public virtual void Subscribe(object instance)
{
lock (handlers)
{
if (handlers.Any(x => x.Matches(instance)))
return;
handlers.Add(new Handler(instance));
}
}
/// <summary>
/// Unsubscribes the instance from all events.
/// </summary>
/// <param name = "instance">The instance to unsubscribe.</param>
public virtual void Unsubscribe(object instance)
{
lock (handlers)
{
var found = handlers.FirstOrDefault(x => x.Matches(instance));
if (found != null)
handlers.Remove(found);
}
}
/// <summary>
/// Publishes a message.
/// </summary>
/// <param name = "message">The message instance.</param>
/// <remarks>
/// Does not marshall the the publication to any special thread by default.
/// </remarks>
public virtual void Publish(object message)
{
Publish(message, PublicationThreadMarshaller);
}
/// <summary>
/// Publishes a message.
/// </summary>
/// <param name = "message">The message instance.</param>
/// <param name = "marshal">Allows the publisher to provide a custom thread marshaller for the message publication.</param>
public virtual void Publish(object message, Action<System.Action> marshal)
{
Handler[] toNotify;
lock (handlers)
toNotify = handlers.ToArray();
marshal(() =>
{
var messageType = message.GetType();
var dead = toNotify
.Where(handler => !handler.Handle(messageType, message))
.ToList();
if (dead.Any())
{
lock (handlers)
{
foreach (var handler in dead)
{
handlers.Remove(handler);
}
}
}
});
}
protected class Handler
{
readonly WeakReference reference;
readonly Dictionary<Type, MethodInfo> supportedHandlers = new Dictionary<Type, MethodInfo>();
public Handler(object handler)
{
reference = new WeakReference(handler);
var interfaces = handler.GetType().GetInterfaces()
.Where(x => typeof(IHandle).IsAssignableFrom(x) && x.IsGenericType);
foreach (var #interface in interfaces)
{
var type = #interface.GetGenericArguments()[0];
var method = #interface.GetMethod("Handle");
supportedHandlers[type] = method;
}
}
public bool Matches(object instance)
{
return reference.Target == instance;
}
public bool Handle(Type messageType, object message)
{
var target = reference.Target;
if (target == null)
return false;
foreach (var pair in supportedHandlers)
{
if (pair.Key.IsAssignableFrom(messageType))
{
pair.Value.Invoke(target, new[] { message });
return true;
}
}
return true;
}
}
}
}
Copy this class in your project.
Then create a simple message class which will represent a message to any listener that a button needs to be enabled:
public class EnableButtonMessage
{
}
Your ExampleA class then needs to implement the IHandle<EnableButtonMessage> interface (in the constructor you need to subscribe that this class is receiving some kind of messages, and the Handle method executes when an actual message of a registered type is received in which you then enable the button):
public sealed class ExampleA : UserControl, IHandle<EnableButtonMessage>
{
...
public ExampleA()
{
this.InitializeComponent();
EventAggregator.Instance.Subscribe(this);
}
...
public void Handle(EnableButtonMessage message)
{
this.OKButton.IsEnabled = true;
}
...
}
In ExampleB class' LostFocus method you just simply call to change your button property:
EventAggregator.Instance.Publish(new EnableButtonMessage());
This is a simple implementation of the EventAggregator pattern and you can then use if for any messaging between any entities inside your application globally. This is a powerful pattern and when used alongside MVVM can make your life a lot easier.

Wait for MahApps Metro Dialog to return result

I have a ViewModel sending a message (using MVVM Light Messenger) to the View to show a Metro Dialog as follows:
In ViewModel, I call this code from the DialogBox class:
DialogBox.ShowDialogBox(
(result) => { DialogResult(result); },
"Dialog Title",
"Dialog Message",
MessageDialogStyle.AffirmativeAndNegative
);
This is the DialogBox Class which deals with sending the message to the View:
public class DialogBox
{
public Action<MessageDialogResult> dialogResultCallBack { get; set; }
public string dialogTitle;
public string dialogText;
public MessageDialogStyle dialogStyle;
public string okButtonText;
public string cancelButtonText;
public DialogBox(Action<MessageDialogResult> _dialogResultCallBack, string _dialogTitle, string _dialogText, MessageDialogStyle _dialogStyle, string _okButtonText, string _cancelButtonText)
{
dialogResultCallBack = _dialogResultCallBack;
dialogTitle = _dialogTitle;
dialogText = _dialogText;
dialogStyle = _dialogStyle;
okButtonText = _okButtonText;
cancelButtonText = _cancelButtonText;
}
public static void ShowDialogBox(Action<MessageDialogResult> _dialogResultCallBack, string _dialogTitle, string _dialogText,
MessageDialogStyle _dialogStyle, string _affirmativeButtonText = "OK", string _negativeButtonText = "Cancel")
{
Messenger.Default.Send(new DialogBox(
_dialogResultCallBack,
_dialogTitle,
_dialogText,
_dialogStyle,
_affirmativeButtonText,
_negativeButtonText), GlobalResources.MessengerTokens.dialogTokenMainWindow);
}
}
The View codebehind has the following code to receive the message:
Messenger.Default.Register<DialogBox>(this, GlobalResources.MessengerTokens.dialogTokenMainWindow, dialogData =>
{
ShowMessageDialog(dialogData);
});
And the ShowMessageDialog deals with showing the actual dialog. These all work fine.
Currently, when the user has selected either Affirmative/Negative, the result is returned and triggers an action call to DialogResult(result) in the ViewModel as seen in the topmost code snippet.
private void DialogResult(MessageDialogResult result)
{
if (result == MessageDialogResult.Affirmative)
{
//deal with the situation
}
else
{
//deal with the situation
}
}
I would actually like to wait for the result straight away after calling the DialogBox.ShowDialogBox() method in the ViewModel. The current approach is causing the code to jump to a separate method call rather than being able to deal with the result straight away. To illustrate this briefly,
if(condition)
{
DialogBox.ShowDialogBox(...);
//Needs some sort of await method to wait for results here
if(result == MessageDialogResult.Affirmative)
{
//do stuff
}
else
{
//do stuff
}
}
I have seen some sample code at least on WinForms that waiting for a result is easier (using codebehind and without MVVM) by doing something like:
if (MessageBox.Show("Title", "Message", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning) == System.Windows.Forms.DialogResult.OK)
Could there be a similar approach I could take for my current situation? Thanks for any suggestions and sorry if my question was too long.
I think there is a better way of doing what you are doing. To make this more MVVM, this is what I do... Firstly, I use Caliburn Micro to handle my MVVM stuff and MEF. So first we have two interfaces:
internal interface IDialogViewModel
{
event EventHandler Closed;
}
and the following interface will help you get results for your dialog
public interface IDialogManager
{
/// <summary>
/// Show a dialog that performs as Task with generic return type.
/// </summary>
/// <typeparam name="TResult">The result to be returned from the dialog task.</typeparam>
/// <param name="viewModel">The DialogViewModel type to be displayed.</param>
/// <returns>The Task to be awaited.</returns>
Task<TResult> ShowDialog<TResult>(DialogViewModel<TResult> viewModel);
/// <summary>
/// Show a dialog that performs as Task.
/// </summary>
/// <param name="viewModel">The result to be returned from the dialog task.</param>
/// <returns>The Task to be awaited.</returns>
Task ShowDialog(DialogViewModel viewModel);
}
The implementation of these interfaces are
/// <summary>
/// DialogViewModel class which should be inherited for all view
/// model that want to be displayed as metro dialogs.
/// </summary>
public abstract class DialogViewModel : Screen, IDialogViewModel
{
private readonly TaskCompletionSource<int> tcs;
internal Task Task { get { return tcs.Task; } }
/// <summary>
/// Deafult constructor.
/// </summary>
protected DialogViewModel()
{
tcs = new TaskCompletionSource<int>();
}
/// <summary>
/// Close the dialog.
/// </summary>
protected void Close()
{
tcs.SetResult(0);
var handler = Closed;
if (handler != null)
handler(this, EventArgs.Empty);
}
/// <summary>
/// Closed event.
/// </summary>
public event EventHandler Closed;
}
/// <summary>
/// DialogViewModel class which should be inherited for all view
/// model that want to be displayed as metro dialogs that can return a
/// specific result.
/// </summary>
public abstract class DialogViewModel<TResult> : Screen, IDialogViewModel
{
private readonly TaskCompletionSource<TResult> tcs;
internal Task<TResult> Task { get { return tcs.Task; } }
/// <summary>
/// Deafult constructor.
/// </summary>
protected DialogViewModel()
{
tcs = new TaskCompletionSource<TResult>();
}
/// <summary>
/// Close the dialog.
/// </summary>
protected void Close(TResult result)
{
tcs.SetResult(result);
var handler = Closed;
if (handler != null)
handler(this, EventArgs.Empty);
}
/// <summary>
/// Closed event.
/// </summary>
public event EventHandler Closed;
}
and the manager class is
/// <summary>
/// The DialogManager that can be used to show Views as Metro modal dialogs.
/// Import IDialogManager to any view model that needs to show a metro message
/// box.
/// </summary>
[Export(typeof(IDialogManager))]
public class DialogManager : IDialogManager
{
/// <summary>
/// Show the required dialog.
/// </summary>
/// <param name="viewModel">The view model ascociated with the view.</param>
public async Task ShowDialog(DialogViewModel viewModel)
{
// Locate the ascociated view.
var viewType = ViewLocator.LocateTypeForModelType(viewModel.GetType(), null, null);
var dialog = (BaseMetroDialog)Activator.CreateInstance(viewType);
if (dialog == null)
{
throw new InvalidOperationException(
String.Format("The view {0} belonging to view model {1} " +
"does not inherit from {2}",
viewType,
viewModel.GetType(),
typeof(BaseMetroDialog)));
}
dialog.DataContext = viewModel;
// Show the metro window.
MetroWindow firstMetroWindow =
Application.Current.Windows.OfType<MetroWindow>().First();
await firstMetroWindow.ShowMetroDialogAsync(dialog);
await viewModel.Task;
await firstMetroWindow.HideMetroDialogAsync(dialog);
}
/// <summary>
/// Show the required dialog.
/// </summary>
/// <typeparam name="TResult">The type of result to return.</typeparam>
/// <param name="viewModel">The view model ascociated with the view.</param>
public async Task<TResult> ShowDialog<TResult>(DialogViewModel<TResult> viewModel)
{
// Locate the ascociated view.
var viewType = ViewLocator.LocateTypeForModelType(viewModel.GetType(), null, null);
var dialog = (BaseMetroDialog)Activator.CreateInstance(viewType);
if (dialog == null)
{
throw new InvalidOperationException(
String.Format("The view {0} belonging to view model {1} " +
"does not inherit from {2}",
viewType,
viewModel.GetType(),
typeof(BaseMetroDialog)));
}
dialog.DataContext = viewModel;
// Show the metro window.
MetroWindow firstMetroWindow = Application.Current.Windows.OfType<MetroWindow>().First();
await firstMetroWindow.ShowMetroDialogAsync(dialog);
TResult result = await viewModel.Task;
await firstMetroWindow.HideMetroDialogAsync(dialog);
return result;
}
}
We also have the message box settings
/// <summary>
/// Class that holds the settings for message box dialogs.
/// </summary>
public class MessageBoxSettings
{
/// <summary>
/// Default constructor.
/// </summary>
public MessageBoxSettings()
{
this.AffirmativeButtonText = "OK";
this.NegativeButtonText = "Cancel";
this.MessageDialogStyle = MessageDialogStyle.AffirmativeAndNegative;
this.MetroColorDialogScheme = MetroDialogColorScheme.Theme;
this.Animation = false;
}
/// <summary>
/// Sets the button styles to use.
/// Default is 'OK' and 'Cancel'.
/// </summary>
public MessageDialogStyle MessageDialogStyle { get; set; }
/// <summary>
/// The color sheme to use for the dialog.
/// </summary>
public MetroDialogColorScheme MetroColorDialogScheme { get; set; }
/// <summary>
/// Affirmative button text. Default OK.
/// </summary>
public string AffirmativeButtonText { get; set; }
/// <summary>
/// The negative button text to use.
/// </summary>
public string NegativeButtonText { get; set; }
/// <summary>
/// First auxillary button text.
/// </summary>
public string FirstAuxillaryButtonText { get; set; }
/// <summary>
/// Second auxillary button text.
/// </summary>
public string SecondAuxiliaryButtonText { get; set; }
/// <summary>
/// Show animation on the dialog.
/// </summary>
public bool Animation { get; set; }
}
Now the view and view model that actually use the code above are
/// <summary>
/// View model for the message box view.
/// </summary>
public class MessageBoxViewModel : DialogViewModel<MessageDialogResult>
{
private MessageBoxSettings settings;
#region Initialisation.
/// <summary>
/// Null.
/// </summary>
public MessageBoxViewModel() { }
/// <summary>
/// Default constructor.
/// </summary>
/// <param name="title">The title of the message box dialog.</param>
/// <param name="message">The message to display in the message box.</param>
public MessageBoxViewModel(string title, string message)
{
this.Title = title;
this.DialogBody = message;
if (this.settings == null)
this.settings = new MessageBoxSettings();
SetDialogVisuals();
}
/// <summary>
/// Overloaded.
/// </summary>
/// <param name="title">The title of the message box dialog.</param>
/// <param name="message">The message to display in the message box.</param>
/// <param name="settings">MessageBoxSettings for the dialog.</param>
public MessageBoxViewModel(string title, string message, MessageBoxSettings settings)
{
this.Title = title;
this.DialogBody = message;
this.settings = settings;
SetDialogVisuals();
}
#endregion // Initialisation.
#region Class Methods.
/// <summary>
/// Set the dialog visuals based on the MessageBoxSettings.
/// </summary>
private void SetDialogVisuals()
{
// Set dialog button visibility.
switch (settings.MessageDialogStyle)
{
case MessageDialogStyle.Affirmative:
this.AffirmativeButtonVisibility = Visibility.Visible;
this.NegativeButtonVisibility = Visibility.Collapsed;
this.FirstAuxillaryButtonVisibility = Visibility.Collapsed;
this.SecondAuxillaryButtonVisibility = Visibility.Collapsed;
break;
case MessageDialogStyle.AffirmativeAndNegative:
this.AffirmativeButtonVisibility = Visibility.Visible;
this.NegativeButtonVisibility = Visibility.Visible;
this.FirstAuxillaryButtonVisibility = Visibility.Collapsed;
this.SecondAuxillaryButtonVisibility = Visibility.Collapsed;
break;
case MessageDialogStyle.AffirmativeAndNegativeAndDoubleAuxiliary:
this.AffirmativeButtonVisibility = Visibility.Visible;
this.NegativeButtonVisibility = Visibility.Visible;
this.FirstAuxillaryButtonVisibility = Visibility.Visible;
this.SecondAuxillaryButtonVisibility = Visibility.Visible;
break;
case MessageDialogStyle.AffirmativeAndNegativeAndSingleAuxiliary:
this.AffirmativeButtonVisibility = Visibility.Visible;
this.NegativeButtonVisibility = Visibility.Visible;
this.FirstAuxillaryButtonVisibility = Visibility.Visible;
this.SecondAuxillaryButtonVisibility = Visibility.Collapsed;
break;
default:
break;
}
// Set the button text.
this.AffirmativeButtonText = settings.AffirmativeButtonText;
this.NegativeButtonText = settings.NegativeButtonText;
this.FirstAuxillaryButtonText = settings.FirstAuxillaryButtonText;
this.SecondAuxiliaryButtonText = settings.SecondAuxiliaryButtonText;
// Color scheme.
string name = MahApps.Metro.ThemeManager.DetectAppStyle(Application.Current).Item2.Name;
this.Background = settings.MetroColorDialogScheme == MetroDialogColorScheme.Theme ?
MahApps.Metro.ThemeManager.Accents
.Where(a => a.Name.CompareNoCase(name))
.First().Resources["HighlightBrush"] as SolidColorBrush :
new SolidColorBrush(System.Windows.Media.Colors.White);
}
/// <summary>
/// Handles the button click events for the affermative button.
/// </summary>
public void AffirmativeButtonClick()
{
Close(MessageDialogResult.Affirmative);
}
/// <summary>
/// Handles the button click events for the negative button.
/// </summary>
public void NegativeButtonClick()
{
Close(MessageDialogResult.Negative);
}
/// <summary>
/// Handles the button click events for the first auxillary button.
/// </summary>
public void FirstAuxillaryButtonClick()
{
Close(MessageDialogResult.FirstAuxiliary);
}
/// <summary>
/// Handles the button click events for the second auxillary button.
/// </summary>
public void SecondAuxillaryButtonClick()
{
Close(MessageDialogResult.SecondAuxiliary);
}
#endregion // Class Methods.
#region Properties.
/// <summary>
/// Hold the dialog title.
/// </summary>
private string title;
public string Title
{
get { return title; }
set
{
if (title == value)
return;
title = value;
NotifyOfPropertyChange(() => Title);
}
}
/// <summary>
/// Hold the text for the dialog body.
/// </summary>
private string dialogBody;
public string DialogBody
{
get { return dialogBody; }
set
{
if (dialogBody == value)
return;
dialogBody = value;
NotifyOfPropertyChange(() => DialogBody);
}
}
/// <summary>
/// Sets the button styles to use.
/// Default is 'OK' and 'Cancel'.
/// </summary>
private MessageDialogStyle messageDialogStyle =
MessageDialogStyle.AffirmativeAndNegative;
public MessageDialogStyle MessageDialogStyle
{
get { return messageDialogStyle; }
set
{
if (messageDialogStyle == value)
return;
messageDialogStyle = value;
NotifyOfPropertyChange(() => MessageDialogStyle);
}
}
/// <summary>
/// The color sheme to use for the dialog.
/// </summary>
private SolidColorBrush background;
public SolidColorBrush Background
{
get { return background; }
set
{
if (background == value)
return;
background = value;
NotifyOfPropertyChange(() => Background);
}
}
/// <summary>
/// Affirmative button text. Default OK.
/// </summary>
private string affirmativeButtonText = "OK";
public string AffirmativeButtonText
{
get { return affirmativeButtonText; }
set
{
if (affirmativeButtonText == value)
return;
affirmativeButtonText = value;
NotifyOfPropertyChange(() => AffirmativeButtonText);
}
}
/// <summary>
/// Visibility for the default affirmative button.
/// </summary>
private Visibility affirmativeButtonVisibility = Visibility.Visible;
public Visibility AffirmativeButtonVisibility
{
get { return affirmativeButtonVisibility = Visibility.Visible; }
set
{
if (affirmativeButtonVisibility == value)
return;
affirmativeButtonVisibility = value;
NotifyOfPropertyChange(() => AffirmativeButtonVisibility);
}
}
/// <summary>
/// The negative button text to use.
/// </summary>
private string negativeButtonText = "Cancel";
public string NegativeButtonText
{
get { return negativeButtonText; }
set
{
if (negativeButtonText == value)
return;
negativeButtonText = value;
NotifyOfPropertyChange(() => NegativeButtonText);
}
}
/// <summary>
/// Visibility for the default negative button.
/// </summary>
private Visibility negativeButtonVisibility = Visibility.Visible;
public Visibility NegativeButtonVisibility
{
get { return negativeButtonVisibility; }
set
{
if (negativeButtonVisibility == value)
return;
negativeButtonVisibility = value;
NotifyOfPropertyChange(() => NegativeButtonVisibility);
}
}
/// <summary>
/// First auxillary button text.
/// </summary>
private string firstAuxillaryButtonText;
public string FirstAuxillaryButtonText
{
get { return firstAuxillaryButtonText; }
set
{
if (firstAuxillaryButtonText == value)
return;
firstAuxillaryButtonText = value;
NotifyOfPropertyChange(() => FirstAuxillaryButtonText);
}
}
/// <summary>
/// First auxillary button visibility.
/// </summary>
private Visibility firstAuxillaryButtonVisibility = Visibility.Collapsed;
public Visibility FirstAuxillaryButtonVisibility
{
get { return firstAuxillaryButtonVisibility; }
set
{
if (firstAuxillaryButtonVisibility == value)
return;
firstAuxillaryButtonVisibility = value;
NotifyOfPropertyChange(() => FirstAuxillaryButtonVisibility);
}
}
/// <summary>
/// Second auxillary button text.
/// </summary>
private string secondAuxiliaryButtonText;
public string SecondAuxiliaryButtonText
{
get { return secondAuxiliaryButtonText; }
set
{
if (secondAuxiliaryButtonText == value)
return;
secondAuxiliaryButtonText = value;
NotifyOfPropertyChange(() => SecondAuxiliaryButtonText);
}
}
/// <summary>
/// Second auxillary button visibility.
/// </summary>
private Visibility secondAuxillaryButtonVisibility = Visibility.Collapsed;
public Visibility SecondAuxillaryButtonVisibility
{
get { return secondAuxillaryButtonVisibility; }
set
{
if (secondAuxillaryButtonVisibility == value)
return;
secondAuxillaryButtonVisibility = value;
NotifyOfPropertyChange(() => SecondAuxillaryButtonVisibility);
}
}
#endregion // Properties.
}
The view is
<MahAppsDialogs:CustomDialog
x:Class="GambitFramework.Core.MessageBox.MessageBoxView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Caliburn="http://www.caliburnproject.org"
xmlns:MahApps="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:Local="clr-namespace:GambitFramework.Core.MessageBox"
xmlns:Converters="clr-namespace:GambitFramework.Core.Converters;assembly=GambitFramework"
xmlns:MahAppsDialogs="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro"
Title="{Binding Title}">
<MahAppsDialogs:CustomDialog.Content>
<TextBlock Text="{Binding DialogBody}"
Margin="0,5,0,0"
TextWrapping="Wrap"/>
</MahAppsDialogs:CustomDialog.Content>
<MahAppsDialogs:CustomDialog.DialogBottom>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="25*" />
<ColumnDefinition Width="50*" />
<ColumnDefinition Width="25*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="1"
Orientation="Horizontal"
HorizontalAlignment="Right"
Margin="0,0,0,0">
<Button x:Name="AffirmativeButton"
Content="{Binding AffirmativeButtonText}"
Visibility="{Binding AffirmativeButtonVisibility}"
Style="{StaticResource AccentedSquareButtonStyle}"
Caliburn:Message.Attach="[Event Click] = [Action AffirmativeButtonClick()]"
MinWidth="75"
Padding="15,0"
Margin="5,10,0,5"/>
<Button x:Name="NegativeButton"
Content="{Binding NegativeButtonText}"
Visibility="{Binding NegativeButtonVisibility}"
Caliburn:Message.Attach="[Event Click] = [Action NegativeButtonClick()]"
MinWidth="75"
Padding="15,0"
Margin="10,10,0,5"/>
<Button x:Name="FirstAuxiliaryButton"
Content="{Binding FirstAuxillaryButtonText}"
Visibility="{Binding FirstAuxillaryButtonVisibility}"
Caliburn:Message.Attach="[Event Click] = [Action FirstAuxillaryButtonClick()]"
MinWidth="75"
Padding="15,0"
Margin="5,10,0,5"/>
<Button x:Name="SecondAuxiliaryButton"
Content="{Binding SecondAuxiliaryButtonText}"
Visibility="{Binding SecondAuxillaryButtonVisibility}"
Caliburn:Message.Attach="[Event Click] = [Action SecondAuxillaryButtonClick()]"
MinWidth="75"
Padding="15,0"
Margin="5,10,0,5"/>
</StackPanel>
</Grid>
</MahAppsDialogs:CustomDialog.DialogBottom>
</MahAppsDialogs:CustomDialog>
This view has an empty code behind. This code can then be used as follows
MessageBoxSettings settings = new MessageBoxSettings()
{
MessageDialogStyle = MessageDialogStyle.AffirmativeAndNegative,
MetroColorDialogScheme = MetroDialogColorScheme.Accented,
AffirmativeButtonText = "Delete",
NegativeButtonText = "Cancel"
};
string message = String.Format(
"Are you sure you want to delete back test \"{0}\" {1}",
SelectedBackTest.Name,
SelectedBackTest.LastRun == null ?
String.Empty :
String.Format("which was late run on {0:G}?", SelectedBackTest.LastRun));
MessageDialogResult r = await dialogManager
.ShowDialog<MessageDialogResult>(
new MessageBoxViewModel("Confirm Delete", message, settings));
if (r == MessageDialogResult.Affirmative)
{
...
}
I hope this helps.
The accepted answer requires some extra library AND a ridiculous amount of code.
Here is what I did to wait for the dialog to complete, using MahApps only.
using SysThread = System.Threading;
using WpfThread = System.Windows.Threading;
using SysTasks = System.Threading.Tasks;
using MahCtl = MahApps.Metro.Controls;
using MahDlg = MahApps.Metro.Controls.Dialogs;
using Win32 = Microsoft.Win32;
using Wpf = System.Windows;
using SysCompMod = System.ComponentModel;
[...]
MahCtl.MetroWindow parentMetroWindow = Wpf.Application.Current.Windows.OfType<MahCtl.MetroWindow>().First();
var metroDialogSettings = new MahDlg.MetroDialogSettings();
metroDialogSettings.OwnerCanCloseWithDialog = true; //does not appear to have any effect
metroDialogSettings.AnimateHide = false;
metroDialogSettings.AnimateShow = false;
[...]
using( SysThread.CancellationTokenSource tokenSource = new SysThread.CancellationTokenSource() )
{
metroDialogSettings.CancellationToken = tokenSource.Token;
SysTasks.Task<MahDlg.MessageDialogResult> task = MahDlg.DialogManager.ShowMessageAsync( parentMetroWindow, title, message, mahStyle, metroDialogSettings );
// ReSharper disable once AccessToDisposedClosure
SysCompMod.CancelEventHandler cancelEventHandler = (s, e) => tokenSource.Cancel();
parentMetroWindow.Closing += cancelEventHandler;
while( !(task.IsCompleted || task.IsCanceled || task.IsFaulted) )
Wpf.Application.Current.Dispatcher.Invoke( WpfThread.DispatcherPriority.Background, new Action( delegate { } ) );
parentMetroWindow.Closing -= cancelEventHandler;
return responseFromMahAppsMessageDialogResult( type, task.Result );
}
The cancelEventHandler is necessary in case the user attempts to close your main window via the taskbar while the modal dialog is up.

Categories

Resources