I'm currently using Caliburn.Micro 2.0 for my Windows Phone 8.1 project (Universal App) and I'm having problem with conditionally cancelling page close after user clicks a MessageDialog button.
It seems that Caliburn closes page after leaving CanClose() method, not waiting for the callback which is called after async MessageDialog.
public class MyViewModel: Screen
{
public override async void CanClose(Action<bool> callback)
{
MessageDialog dlg = new MessageDialog("Close?","Confirmation");
dlg.Commands.Add(new UICommand() { Id = 0, Label = "Yes" });
dlg.Commands.Add(new UICommand() { Id = 1, Label = "No" });
var result = await dlg.ShowAsync();
callback((int)result.Id == 0);
}
}
The only solution I have at the moment is set a field with a flag indicating if the page can be closed. On the user attempt to navigate back I tell Caliburn to abort the close and I display the confirmation dialog. When I get the result I set the flag to true and navigate back manually. This causes another call to CanClose, but this time I set the callback to true and skip the dialog part.
I don't like this solution much, but it is only way I managed to solve this problem.
private bool canClose = false;
public override async void CanClose(Action<bool> callback)
{
callback(canClose);
if (!canClose)
{
MessageDialog dlg = new MessageDialog("Close?","Confirmation");
dlg.Commands.Add(new UICommand() { Id = 0, Label = "Yes" });
dlg.Commands.Add(new UICommand() { Id = 1, Label = "No" });
var result = await dlg.ShowAsync();
if ((int)result.Id == 0)
{
canClose = true;
navigationService.GoBack();
}
}
}
PS: I don't use MessageDialog directly in my ViewModel, I'm using a dialog service interface for popups. I just used it here to demonstrate the issue.
While the enhancement CanClose isn't set, this is my approach utilize Navigating event to solve this 'problem'
If user could just GoBack() would be easy to handle, but in my case, there's many options to navigate. So, the only way that I found to solve it is described below:
public MyViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
_navigationService.Navigating += OnGoBack;
}
private async void OnGoBack(object sender, NavigatingCancelEventArgs e)
{
e.Cancel = true;
var dlg = new MessageDialog("Close?", "Confirmation"); //Dialog for demo purpose only!
dlg.Commands.Add(new UICommand() { Id = 0, Label = "Yes" });
dlg.Commands.Add(new UICommand() { Id = 1, Label = "No" });
var result = await dlg.ShowAsync();
if ((int) result.Id != 0) return;
_navigationService.Navigating -= OnGoBack;
if (e.NavigationMode == NavigationMode.Back)
_navigationService.GoBack();
else
{
var myViewModel = Type.GetType($"YourNameSpaceViewModels.{e.SourcePageType.Name}Model");
_navigationService.NavigateToViewModel(myViewModel);
}
}
Explaination:
$"YourNameSpaceViewModels.{e.SourcePageType.Name}Model"
Here I get the full path to my class where user want to goto
And so, I navigate to it _navigationService.NavigateToViewModel(myViewModel);
Related
I am currently working on a matching card game where I need to store the images on Firebase. I am uploading the images by a button click when I start the game(doing it automatically creates same problem but the button one is safer) I think the Image isn't getting downloaded fast enough to show on the card face or it might not be working in a sequence with the whole app so the bitmap array gets zero elements inside. My current code is:
class game2x2 : AppCompatActivity() {
private lateinit var database: DatabaseReference
private lateinit var buttons: List<ImageButton>
//private lateinit var bitmapArray: ArrayList<Bitmap>
private var bitmapArray = mutableListOf<Bitmap>()
private lateinit var button1: ImageButton
private lateinit var button2: ImageButton
private lateinit var button3: ImageButton
private lateinit var button4: ImageButton
private lateinit var upload: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_game2x2)
val min = 1
val max = 45
val database = FirebaseDatabase.getInstance()
val imageID1 = Random().nextInt(max - min + 1) + min
val imageID2 = Random().nextInt(max - min + 1) + min
val aDatabase = FirebaseStorage.getInstance().getReference("all/$imageID1.jpg")
val sDatabase = FirebaseStorage.getInstance().getReference("all/$imageID2.jpg")
upload = findViewById(R.id.uploadButton)
button1 = findViewById(R.id.imageButton1)
button2 = findViewById(R.id.imageButton2)
button3 = findViewById(R.id.imageButton3)
button4 = findViewById(R.id.imageButton4)
buttons = listOf(button1, button2, button3, button4)
upload.setOnClickListener(View.OnClickListener {
try {
val localfile = File.createTempFile("tempfile", ".jpg")
aDatabase.getFile(localfile).addOnSuccessListener {
val bitmap = BitmapFactory.decodeFile(localfile.absolutePath)
bitmapArray.add(bitmap)
}.addOnFailureListener {
Log.w("myapplication", "ERROR RETRIEVING IMAGE")
Toast.makeText(this, "ERROR RETRIEVING IMAGE", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
e.printStackTrace()
}
try {
val localfile = File.createTempFile("tempfile1", ".jpg")
sDatabase.getFile(localfile).addOnSuccessListener {
val bitmap = BitmapFactory.decodeFile(localfile.absolutePath)
bitmapArray.add(bitmap)
}.addOnFailureListener {
Log.w("myapplication", "ERROR RETRIEVING IMAGE")
Toast.makeText(this, "ERROR RETRIEVING IMAGE", Toast.LENGTH_SHORT).show()
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
/// DUPLICATE
bitmapArray.addAll(bitmapArray)
///SHUFFLE
bitmapArray.shuffle()
Log.w("myapplication", bitmapArray.size.toString())
})
buttons.forEachIndexed { index, button ->
button.setOnClickListener(View.OnClickListener {
button.setImageBitmap(bitmapArray[index])
})
}
}
}
Is there any other way to retrieve image from the Firebase Storage besides downloading and adding it to a temporary file and then decoding it to a bitmap?
I tried anything that I could find. I even tried adding the access tokens of the images to a realtime database and then getting them from there but I failed terribly. Thanks in advance for helping!
Since getFile() an asynchronous task I would imagine your log statement Log.w("myapplication", bitmapArray.size.toString()) is executing while the bitmapArray is still empty? This would happen because the aDatabase.getFile().addOnSuccessListener {} and sDatabase.getFile().addOnSuccessListener {} won't execute until the download finishes, but allow the rest of your function to continue to execute.
What you need to do is await the results of the downloads before continuing with the duplicate and shuffle portions.
getFile() returns a FileDownloadTask, which inherits from StorageTask. StorageTask has an isComplete() method -- and a few others the may be useful for errors cases. One option would be to capture the FileDownloadTask in a variable and not continue executing until your downloads are finished. However, be warned this might freeze up your main thread.
Edit: Instead of checking status on the main thread, you might want to try something like disabling the buttons until the images are ready. See edit comments:
class game2x2 : AppCompatActivity() {
private lateinit var database: DatabaseReference
private lateinit var buttons: List<ImageButton>
//private lateinit var bitmapArray: ArrayList<Bitmap>
private var bitmapArray = mutableListOf<Bitmap>()
private lateinit var button1: ImageButton
private lateinit var button2: ImageButton
private lateinit var button3: ImageButton
private lateinit var button4: ImageButton
private val numImages = 2 // EDIT total number of images we need to download
private val numImagesReady = AtomicInteger(0) // EDIT count of how many images are currently ready
private lateinit var upload: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_game2x2)
val min = 1
val max = 45
val database = FirebaseDatabase.getInstance()
val imageID1 = Random().nextInt(max - min + 1) + min
val imageID2 = Random().nextInt(max - min + 1) + min
val aDatabase = FirebaseStorage.getInstance().getReference("all/$imageID1.jpg")
val sDatabase = FirebaseStorage.getInstance().getReference("all/$imageID2.jpg")
upload = findViewById(R.id.uploadButton)
button1 = findViewById(R.id.imageButton1)
button2 = findViewById(R.id.imageButton2)
button3 = findViewById(R.id.imageButton3)
button4 = findViewById(R.id.imageButton4)
buttons = listOf(button1, button2, button3, button4)
// EDIT disable buttons until all images are ready
buttons.forEach {
it.setEnabled(false)
}
upload.setOnClickListener(View.OnClickListener {
try {
val localfile = File.createTempFile("tempfile", ".jpg")
aDatabase.getFile(localfile).addOnSuccessListener {
val bitmap = BitmapFactory.decodeFile(localfile.absolutePath)
bitmapArray.add(bitmap)
// EDIT add the image twice here instead of duplicating later
bitmapArray.add(bitmap)
// EDIT count this image as ready
val totalImagesReady = numImagesReady.incrementAndGet()
// EDIT once all images are ready, shuffle and enable the buttons
if (totalImagesReady == numImages) {
bitmapArray.shuffle()
buttons.forEach { it.setEnabled(true) }
}
}.addOnFailureListener {
Log.w("myapplication", "ERROR RETRIEVING IMAGE")
Toast.makeText(this, "ERROR RETRIEVING IMAGE", Toast.LENGTH_SHORT).show()
}
} catch (e: Exception) {
e.printStackTrace()
}
try {
// SUGGESTION especially if this will be implemented 8x8, you might want to try implementing this in a loop instead of duplicating code
val localfile = File.createTempFile("tempfile1", ".jpg")
sDatabase.getFile(localfile).addOnSuccessListener {
val bitmap = BitmapFactory.decodeFile(localfile.absolutePath)
bitmapArray.add(bitmap)
// EDIT add the image twice here instead of duplicating later
bitmapArray.add(bitmap)
// EDIT count this image as ready
val totalImagesReady = numImagesReady.incrementAndGet()
// EDIT once all images are ready, shuffle and enable the buttons
if (totalImagesReady == numImages) {
bitmapArray.shuffle()
buttons.forEach { it.setEnabled(true) }
}
}.addOnFailureListener {
Log.w("myapplication", "ERROR RETRIEVING IMAGE")
Toast.makeText(this, "ERROR RETRIEVING IMAGE", Toast.LENGTH_SHORT).show()
}
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
// EDIT moved /// DUPLICATE
// EDIT refactor bitmapArray.addAll(bitmapArray)
// EDIT moved ///SHUFFLE
// EDIT moved bitmapArray.shuffle()
// EDIT remove Log.w("myapplication", bitmapArray.size.toString())
})
buttons.forEachIndexed { index, button ->
button.setOnClickListener(View.OnClickListener {
button.setImageBitmap(bitmapArray[index])
})
}
}
}
How can i make custom Prompt?
I tried with code below..
public static string ShowDialog(string text, string caption) {
Form prompt = new Form() {
Width = 500,
Height = 150,
FormBorderStyle = FormBorderStyle.FixedDialog,
Text = caption,
StartPosition = FormStartPosition.CenterScreen
};
Label textLabel = new Label() { Left = 50, Top = 20, Text = text };
TextBox textBox = new TextBox() { Left = 50, Top = 50, Width = 400 };
Button confirmation = new Button() { Text = "Ok", Left = 350, Width = 100, Top = 70, DialogResult = DialogResult.OK };
confirmation.Click += (sender, e) => { prompt.Close(); };
prompt.Controls.Add(textBox);
prompt.Controls.Add(confirmation);
prompt.Controls.Add(textLabel);
prompt.AcceptButton = confirmation;
return prompt.ShowDialog() == DialogResult.OK ? textBox.Text : "";
}
And then am using it like below
public bool OnJSDialog(IWebBrowser chromiumWebBrowser, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage) {
if(dialogType.ToString() == "Prompt") {
//Form prompt = ShowDialogClass.ShowDialog("as", "asd");
string promptValue = Components.ShowDialog("Test", "123");
if (promptValue != "") {
callback.Continue(true, promptValue);
} else {
callback.Continue(false, "");
};
};
But i am getting error.
System.InvalidOperationException: 'Cross-thread operation not valid: Control '' accessed from a thread other than the thread it was created on.'
return false;
}
How can i implement this dialog to show custom prompt?
Few months too late but, here you go.
You are trying to create a new Form(your prompt form) inside another thread. In this case your CEF browser thread that will create a object from class IJsDialogHandler will be on another thread than the prompt message thread so you have to Cross the thread to access it.
The way you do this is "Invoke"(saying something like "wo wo don't worry, i know what i'm doing"). When you use "Invoke" your asking for a witness, well that witness should have the same kind of capabilities as your prompt message box form so.... in this case form that creates the CEF browser. so the code should be something like this
public bool OnJSDialog(IWebBrowser chromiumWebBrowser, IBrowser browser, string originUrl, CefJsDialogType dialogType, string messageText, string defaultPromptText, IJsDialogCallback callback, ref bool suppressMessage) {
if(dialogType.ToString() == "Prompt") {
if (ParentForm.InvokeRequired)
{
ParentForm.Invoke((MethodInvoker)delegate ()
{
string promptValue = Components.ShowDialog(messageText, "Prompt Message");
if (promptValue != "") {
callback.Continue(true, promptValue);
} else {
callback.Continue(false);
}
}
}
suppressMessage = false;
return true;
}
}
ParentForm should be changed to the name of the form that initialize the CEF browser.
Has anyone used CreateBookmark() with BookmarkOptions.NonBlocking?
I'm trying to use it with MultipleResume option but seems I cannot even resume.
Bookmark activity:
public InArgument<string> BookmarkName { get; set; }
public InArgument<BookmarkOptions> BookmarkOptions { get; set; }
protected override void Execute(NativeActivityContext context)
{
var options = BookmarkOptions.Get(context);
context.CreateBookmark(BookmarkName.Get(context),
ReadCompleteCallback,options);
}
Test Code:
[TestMethod]
public void TestMethod1()
{
InitWorkflow();
wfat = WorkflowApplicationTest.Create(sm);
wfat.TestActivity();
Assert.IsTrue(wfat.WaitForIdleEvent());
var res = wfat.TestWorkflowApplication.ResumeBookmark("First", "data");
Assert.IsTrue(res == BookmarkResumptionResult.Success, "Resumption fail with result:" + res);
Assert.IsTrue(wfat.Bookmarks.Contains("First"), "No first bkmk");
}
private void InitWorkflow()
{
sm = new StateMachine()
{
States =
{ //First state with non blocking bookmark
new State(){
DisplayName = "First",Entry = new BookmarkActivity(){BookmarkName = "First",BookmarkOptions =
BookmarkOptions.NonBlocking | BookmarkOptions.MultipleResume},
Transitions =
{
new Transition(){ }
}
}, //Second state with blocking bookmark
new State(){
DisplayName = "Second",Entry = new BookmarkActivity(){BookmarkName = "Second",BookmarkOptions =
BookmarkOptions.None},
Transitions =
{
new Transition(){ }
}
},
new State(){
DisplayName = "End",
IsFinal = true
}
}
};
sm.InitialState = sm.States[0];
sm.InitialState.Transitions[0].To = sm.States[1];
sm.States[1].Transitions[0].To = sm.States[2];
}
Result of ResumeBookmark in above test code is 'NotFound'
I would appreciate any working code that demonstrates NonBlocking option.
Even NonBlocking bookmarks are removed when the activity that created it is completed. They allow the activity to continue execution but that's it.
Bottom line you've to maintain an activity in a not completed state (usually the outside activity) and everything inside it will execute even when a NonBlocking bookmark is found.
That's why you're getting a NotFound error. The activity that created the bookmark has ended and the bookmark no longer exists.
P.S.: A somehow usual use case for NonBlocing bookmarks is, for example, when you've a long running activity, that might throw exceptions while executing, and that way you've the possibility to resume the workflow at a previous state.
I started looking at the AjaxFileUpload control, specifically the ContextKeys property. However, I do not understand how to use it.
The documentation says of AjaxFileUpload that the ContextKeys is used to pass information to the server when a file is uploaded. But no examples are provided. Are there any examples online that I could look at?
Though such functionality not implemented (I believe it was planned but by some reasons was postponed), nothing protect you from implement it yourself. To do this you need to download AjaxControlToolkit source code and tweak it for your needs.
There will be a lot of points so you may to prepare a cup of coffee before :)
I'll show changes with name of file that must being changed.
Server/AjaxControlToolkit/AjaxFileUpload/AjaxFileUpload.cs file
First of all, add ContextKeys property to the AjaxFileUploadEventArgs.cs file (it located in same folder):
/// <summary>
/// Gets or sets the context keys.
/// </summary>
public string ContextKeys
{
get;
set;
}
After that open the AjaxFileUpload class code and change the OnPreRender method. Here is a part of this method with custom modifications:
var eventArgs = new AjaxFileUploadEventArgs(guid, AjaxFileUploadState.Success,
"Success", uploadedFile.FileName,
uploadedFile.ContentLength, uploadedFile.ContentType,
stream.ToArray());
// NEW CODE HERE
eventArgs.ContextKeys = this.Page.Request.Form["contextKeys"];
That's all changes in server code we need. Now we need to modify the Sys.Extended.UI.AjaxFileUpload client class (file AjaxFileUpload.pre.js )
Firstly let's modify _html5UploadFile method as below:
_html5UploadFile: function (fileItem) {
this._guid = Sys.Extended.UI.AjaxFileUpload.utils.generateGuid();
var uploadableFile = fileItem.get_fileInputElement();
var fd = new FormData();
fd.append("fileId", uploadableFile.id);
fd.append("Filedata", uploadableFile.file);
if (this.contextKeys) {
if (typeof this.contextKeys !== "string") {
this.contextKeys = Sys.Serialization.JavaScriptSerializer.serialize(this.contextKeys);
}
fd.append("contextKeys", this.contextKeys);
}
$common.setVisible(this._progressBar, true);
this._setDisableControls(true);
this._html5SetPercent(0);
this._setStatusMessage(String.format(Sys.Extended.UI.Resources.AjaxFileUpload_UploadingHtml5File, uploadableFile.file.name, Sys.Extended.UI.AjaxFileUpload.utils.sizeToString(uploadableFile.file.size)));
var url = this._postBackUrl;
if (url.indexOf("?") != -1)
url += "&";
else
url += "?";
this._webRequest = new Sys.Net.WebRequest();
this._executor = new Sys.Net.XMLHttpExecutor();
this._webRequest.set_url(url + 'contextkey=' + this._contextKey + '&guid=' + this._guid);
this._webRequest.set_httpVerb("POST");
this._webRequest.add_completed(this.bind(this._html5OnRequestCompleted, this));
//this._executor.add_load(this.bind(this._html5OnComplete, this));
this._executor.add_progress(this.bind(this._html5OnProgress, this));
this._executor.add_uploadAbort(this.bind(this._html5OnAbort, this));
this._executor.add_error(this.bind(this._html5OnError, this));
this._webRequest.set_executor(this._executor);
this._executor.executeRequest(fd);
}
As you can see above, we adding contextKeys to form data, posted with Ajax request.
The we need to modify the _uploadInputElement method:
_uploadInputElement: function (fileItem) {
var inputElement = fileItem.get_fileInputElement();
var uploader = this;
uploader._guid = Sys.Extended.UI.AjaxFileUpload.utils.generateGuid();
setTimeout(function () {
uploader._setStatusMessage(String.format(Sys.Extended.UI.Resources.AjaxFileUpload_UploadingInputFile, Sys.Extended.UI.AjaxFileUpload.utils.getFileName(inputElement.value)));
uploader._setDisableControls(true);
uploader.setThrobber(true);
}, 0);
var url = uploader._postBackUrl;
if (url.indexOf("?") != -1)
url += "&";
else
url += "?";
uploader._createVForm();
uploader._vForm.appendChild(inputElement);
if (this.contextKeys) {
if (typeof this.contextKeys !== "string") {
this.contextKeys = Sys.Serialization.JavaScriptSerializer.serialize(this.contextKeys);
}
var contextKeysInput = document.createElement("input");
contextKeysInput.setAttribute("type", "hidden");
contextKeysInput.setAttribute("name", "contextKeys");
contextKeysInput.setAttribute("value", this.contextKeys);
uploader._vForm.appendChild(contextKeysInput);
}
uploader._vForm.action = url + 'contextkey=' + this._contextKey + '&guid=' + this._guid;
uploader._vForm.target = uploader._iframeName;
setTimeout(function () {
uploader._vForm.submit();
uploader._waitTimer = setTimeout(function () { uploader._wait() }, 100);
}, 0);
}
After all these changes you can set ContextKeys property in code-behind and get it value from AjaxFileUploadEventArgs argument of the UploadComplete event as below:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack && !AjaxFileUpload1.IsInFileUploadPostBack)
{
AjaxFileUpload1.ContextKeys = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(new Dictionary<string, string> { { "1", "First" }, { "2", "Second" } });
}
protected void AjaxFileUpload1_OnUploadComplete(object sender, AjaxFileUploadEventArgs file)
{
if (!string.IsNullOrEmpty(file.ContextKeys))
{
var contextKeys = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Dictionary<string, string>>(file.ContextKeys);
}
Also, if you'll implement OnClientUploadStarted client-side event as proposed here link, you may pass to server your contextKeys from client:
function uploadStarted(sender, args) {
sender.contextKeys = { "first": "1", "second": "2" };
}
Greetings, creating my first MVVM based WPF app and trying to figure out why I'm unable to hook into the PropertyChanged event of a dependency property.
Code in the parent view model:
void createClients()
{
var clients = from client in Repository.GetClients()
select new ClientViewModel(Repository, client);
foreach (var client in clients)
{
client.PropertyChanged += onClientPropertyChanged;
}
Clients = new ViewableCollection<ClientViewModel>(clients);
Clients.CollectionChanged += onClientsCollectionChanged;
}
// Never gets called
void onClientPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Name")
{
//...
}
}
ViewableCollection is a simple extension of ObservableCollection to encapsulate a View.
In the ClientViewModel the setters are being called but RaisePropertyChanged isn't working as I would expect, because onClientPropertyChanged isn't being invoked. Both view models inherit from ViewModelBase.
public string Name
{
get { return client.Name; }
set
{
if (value == client.Name) return;
client.Name = value;
RaisePropertyChanged("Name");
}
}
If I wire up PropertyChanged to a method inside the ClientViewModel then it is being fired, so I'm stumped as to why this isn't working in the parent view model. Where am I going wrong?
This SO question explains the problem; ObservableCollection protects the PropertyChanged event.
One solution is to use MVVM-Light Messenger:
void createClients()
{
var clients = from client in Repository.GetClients()
select new ClientViewModel(Repository, client);
Clients = new ViewableCollection<ClientViewModel>(clients);
Clients.CollectionChanged += onClientsCollectionChanged;
Messenger.Default.Register<PropertyChangedMessage<string>>(this, (pcm) =>
{
var clientVM = pcm.Sender as ClientViewModel;
if (clientVM != null && pcm.PropertyName == "Name")
{
// ...
}
});
}
createClients() should be refactored, but for consistency with the question code I'll leave it in there. Then a slight change to the property setter:
public string Name
{
get { return client.Name; }
set
{
if (value == client.Name) return;
string oldValue = client.Name;
client.Name = value;
RaisePropertyChanged<string>("Name", oldValue, value, true);
}
}