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;
}
}
}
I have created a custom iOS control in a Xamarin project. However, it currently does not draw on to the page when added into the XAML on a Xamarin.Forms page.
<OnPlatform.iOS>
<iOSControls:CellContent BindingContext="{Binding}" CellData="{Binding VM.CellCollection}"/>
</OnPlatform.iOS>
I am also not sure if this is the correct way to bind data from the ViewModel on to the control, and due to the fact it does not load am unable to test this.
The CellContent class is as below
`Public partial class CellContent : UIView
{
#region [ Private Fields ]
/// <summary>
/// The cell data model used for generating each cell
/// </summary>
private ICell cellData;
#endregion
#region [ Constructor ]
/// <summary>
/// Initializes a new instance of the <see cref="CellContent" /> class.
/// </summary>
/// <param name="handle">handle pointer passed to the base class</param>
public CellContent(IntPtr handle) : base(handle)
{
}
#endregion
#region [ Public Properties ]
/// <summary>
/// Gets or sets the cell data model
/// </summary>
[Export("CellData"), Browsable(true)]
public ICell CellData
{
get
{
return this.cellData;
}
set
{
this.cellData = value;
}
}
#endregion
#region [ UIView Events ]
/// <summary>
/// Static control generator
/// </summary>
/// <returns>A control instance</returns>
public static CellContent Create()
{
var arr = NSBundle.MainBundle.LoadNib("CellContent", null, null);
var v = Runtime.GetNSObject<CellContent>(arr.ValueAt(0));
return v;
}
/// <summary>
/// Initialises the sub controls
/// </summary>
public override void AwakeFromNib()
{
base.AwakeFromNib();
HorizontalGridLines horizontalRows = HorizontalGridLines.Create();
VerticalGridLines verticalRows = VerticalGridLines.Create();
PlottedActivity plottedActivity = PlottedActivity.Create();
horizontalRows.VM = this.CellData;
verticalRows.VM = this.CellData;
plottedActivity.VM = this.CellData;
this.AddSubview(horizontalRows);
this.AddSubview(verticalRows);
this.AddSubview(plottedActivity);
}
#endregion`
The horizontal/vertical grid lines and plotted activity files are all basically the same and I have verified they work in a separate project (without the binding or being instantiated from the XAML), but the HorizontalGridLines file can be seen below as well for reference
`public partial class HorizontalGridLines : UIView
{
/// <summary>
/// Initializes a new instance of the <see cref="HorizontalGridLines" /> class.
/// </summary>
/// <param name="handle">handle pointer passed to the base class</param>
public HorizontalGridLines(IntPtr handle) : base(handle)
{
}
/// <summary>
/// Gets or sets the data context cast as an interface for binding
/// </summary>
[Export("CellData"), Browsable(true)]
public ICell CellData{ get; set; }
/// <summary>
/// Static control generator
/// </summary>
/// <returns>A control instance</returns>
public static HorizontalGridLines Create()
{
var arr = NSBundle.MainBundle.LoadNib("HorizontalGridLines", null, null);
var v = Runtime.GetNSObject<HorizontalGridLines>(arr.ValueAt(0));
return v;
}
/// <summary>
/// Drawing override
/// </summary>
/// <param name="rect">The content bounds</param>
public override void Draw(CGRect rect)
{
base.Draw(rect);
int numOfLines = this.CellData.ActivityRows.Count;
var context = UIGraphics.GetCurrentContext();
context.SetLineWidth(2);
context.SetLineDash(0, new nfloat[] { 3, 4 });
UIColor.Black.SetStroke();
int y = 0;
int width = (int)this.Frame.Width;
int height = (int)this.Frame.Height;
int heightPerLine = y = height / numOfLines;
for (int i = 0; i < numOfLines - 1; i++)
{
var path = new CGPath();
CGPoint[] lines = new CGPoint[2];
lines[0] = new PointF(0, y);
lines[1] = new PointF(width, y);
y += heightPerLine;
path.AddLines(lines);
path.CloseSubpath();
context.AddPath(path);
}
context.DrawPath(CGPathDrawingMode.FillStroke);
}
}`
Any help would be greatly appreciated, thanks! :)
So I discovered the way to do this:
On the custom control that you have created, create a public property that you will be assigning your data to.
On the XAML assign the {Binding .} to the public property you have just created providing that they are of the same type.
The data will be accessible from within the custom control during/after the LayoutSubviews() method.
For Example:
<iOSControls:CellContent CellData="{Binding .}"/>
The BindingContext property seems to be a part of standard Xamarin controls rather than custom native controls.
I want to display the public Property/Values of an arbitrary object during runtime in a GUI.
Is there a winform which allows the user to view the content of any object like in debug mode? The object will hold many Dictionaries > and it would be nice to be able to expand and view the contents of those lists during runtime.
If not available, is there someway to achieve something similar?
Thanks
All you need to do is create a form with a PropertyGrid on it. Then set the selected object.
using xxx.CFM.UI.Core;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace xxx.CFM.UI.Forms
{
/// <summary>
/// Form used to inspect objects at runtime
/// </summary>
public partial class frmInspector : BaseForm
{
#region Properties
/// <summary>
/// Gets or Sets the
/// </summary>
public object SelectedObject
{
get { return propertyGrid.SelectedObject; }
set { propertyGrid.SelectedObject = value; }
}
#endregion Properties
#region Constructor
/// <summary>
/// Constructor
/// </summary>
public frmInspector(object objectToInspect)
{
try
{
InitializeComponent();
this.SelectedObject = objectToInspect;
}
catch (Exception ex)
{
UIMessage.DisplayError(ex);
}
}
#endregion Constructor
#region Events
/// <summary>
/// Closes the form
/// </summary>
/// <param name="sender">object</param>
/// <param name="e">args</param>
private void btnClose_Click(object sender, EventArgs e)
{
try
{
this.Close();
}
catch (Exception ex)
{
UIMessage.DisplayError(ex);
}
}
#endregion Events
}
}
I use this on a context menu in a grid for example to expect the data record below it:
/// <summary>
/// Opens the object inspector
/// </summary>
/// <param name="sender">object</param>
/// <param name="e">args</param>
private void inspectorMenuItem_Click(object sender, EventArgs e)
{
try
{
//Get the payload
SectionDataTreeListMenuItemPayload payload = (sender as ToolStripMenuItem).Tag as SectionDataTreeListMenuItemPayload;
if (payload == null || payload.DataRow == null)
return;
using (frmInspector frmInspector = new frmInspector(payload.DataRow))
frmInspector.ShowDialog();
}
catch (Exception ex)
{
UIMessage.DisplayError(ex);
}
}
another little trick you can do is to ensure the form is only available when built in debug mode by using the below code using a 'compiler directive'. (if you want it only used for debugging of course)
#if DEBUG
//Add object inspector menu item if built in debug mode
ToolStripMenuItem inspectorMenuItem = new ToolStripMenuItem();
inspectorMenuItem.Text = "Inspect Object";
inspectorMenuItem.Image = Properties.Resources.Inspect24x24;
inspectorMenuItem.Click += inspectorMenuItem_Click;
inspectorMenuItem.Tag = payload;
contextMenuStrip.Items.Add(inspectorMenuItem);
#endif
There's PropertyGrid:
var form = new Form();
form.Controls.Add
(
new PropertyGrid()
{
SelectedObject = new { A = "Hi", B = new [] { 32, 40 } }
}
);
form.Show();
It's quite far from how the debugger one works, but it can be quite easily modified to handle any special cases you might have.
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.
I am using the RadDataForm which is part of the UI controls from telerik for wp8. The problem I am having I don't think is related to the control but more on something stupid that I have done or not understanding.
I have a class called ProfileFormData and is used to populate DatFields in the DataForm. It obtains its properties from an class called Profile which is in a singleton.
Ok so the code first.
Public class Profile
{
Public sting Name {get;set;}
Public int Gender {get;set;}
}
singleton
public sealed class Globals
{
/// <summary>
/// Current profile
/// </summary>
public Profile profile
private static readonly SingleInstance<Globals> lazy =
new SingleInstance<Globals>(() => new Globals());
public static Globals Instance { get { return lazy.Instance; } }
}
internal sealed class SingleInstance<T> where T : class
{
private readonly Object m_lockObj = new object();
private readonly Func<T> m_delegate;
private Boolean m_isDelegateInvoked;
private T m_value;
/// <summary>
/// Initializes a new instance of the
/// <see cref="SingleInstance<T>"/> class.
/// </summary>
public SingleInstance() : this(() => default(T)) { }
/// <summary>
/// Initializes a new instance of the
/// <see cref="SingleInstance<T>"/> class.
/// </summary>
/// <param name="delegate">The #delegate</param>
public SingleInstance(Func<T> #delegate)
{
m_delegate = #delegate;
}
/// <summary>
/// gets the instance
/// </summary>
/// <value>The instance</value>
public T Instance
{
get
{
if (!m_isDelegateInvoked)
{
T temp = m_delegate();
Interlocked.CompareExchange<T>(ref m_value, temp, null);
Boolean lockTaken = false;
try
{
// WP7 does not support the overload with the
// boolean indicating if the Lock was taken
Monitor.Enter(m_lockObj); lockTaken = true;
m_isDelegateInvoked = true;
}
finally
{
if (lockTaken) { Monitor.Exit(m_lockObj); }
}
}
return m_value;
}
}
}
ProfileFormData
public class ProfileFormData
{
public string Name
{
get { return Globals.Instance.profile.UserName; }
set { Globals.Instance.profile.UserName = value; }
}
[GenericListEditor(typeof(GenderInfoProvider))]
public Gender Gender
{
get { return (Gender)Globals.Instance.profile.Gender; }
set { Globals.Instance.profile.Gender = (int)value; }
}
}
and now the xaml where the error is shown in both blend and vs designer
<Grid Grid.Row="1" Margin="0,6,0,6">
<ScrollViewer>
<telerikInput:RadDataForm Grid.Row="1" Margin="12,0,12,12" x:Name="DataForm">
<telerikInput:RadDataForm.CurrentItem>
<models:ProfileFormData/>
</telerikInput:RadDataForm.CurrentItem>
<Grid>
<telerikInput:DataField Header="Name" TargetProperty="Name"/>
<telerikInput:DataField Header="Gender" TargetProperty="Gender"/>
</Grid>
</telerikInput:RadDataForm>
</ScrollViewer>
</Grid>
all of the xaml above has a red line underneath and I get the error
Object not set to instance of object
Deploying and running the app works fine.
oh and I create the profile like this in the App.xaml.c
/// <summary>
/// Constructor for the Application object.
/// </summary>
public App()
{
// Global handler for uncaught exceptions.
UnhandledException += Application_UnhandledException;
// Standard Silverlight initialization
InitializeComponent();
// Phone-specific initialization
InitializePhoneApplication();
Globals.Instance.profile = new Profile();
}