Prepare data before go to the main view - c#

When the user touch the app icon,
I want do these steps before user go to the main view
Fetch json string from URI
Use JArray.Parse to get the value
After all finish, go to the main view.
The problem is how can I prevent user to go to the main view
and put all the code
I tried to put it in Application_Launching method in the App.xaml.cs file
// Code to execute when the application is launching (eg, from Start)
// This code will not execute when the application is reactivated
private void Application_Launching(object sender, LaunchingEventArgs e)
{
// code here
}
But it doesn't prevent the program to go to the main view before the fetching finished.
And I found that actually in the MainPage.xaml, if I put this code like this
protected override void OnNavigatedTo(NavigationEventArgs e)
{
while(true) {}
// it will prevent the program to go to the main view,
// and will stick with the loading screen until this function reach its end
}
So I think, I can put the all the code here, when I finish the fetch, I will just break the while and it will go to the main view automatically.
And I try, this is the code
protected override void OnNavigatedTo(NavigationEventArgs e)
{
bool isFetchFinished = false;
ObservableCollection<PromoViewModel> Promos = new ObservableCollection<PromoViewModel>();
WebClient client = new WebClient();
client.DownloadStringCompleted += (s, evt) =>
{
if (evt.Error == null)
{
// Retrieve the JSON
string jsonString = evt.Result;
JArray promos = JArray.Parse(jsonString);
foreach (JObject promo in promos)
{
string name = promo["name"].Value<string>();
string description = promo["description"].Value<string>();
string img = promo["image"].Value<string>();
Promos.Add(new PromoViewModel() { Name = name, Description = description, Img = img });
}
isFetchFinished = true;
System.Diagnostics.Debug.WriteLine("finish fetch");
}
};
// run
client.DownloadStringAsync(new Uri("the json url"));
while(true) {
if(isFetchFinished) {
App.ViewModel.LoadData(Promos); // pass value to main view model
break; // after complete, break
}
}
}
I thought it would work, but it was not.
This is what I found,
The WebClient DownloadStringAsync won't run until the OnNavigatedTo function finished.
Because it's still waiting for the while loop to break and reach the end function.
And this
isFetchFinished = true; // will never executed
Resulting infinite loop.
I think I put the fetch code in the wrong method. Where is the right place to put all of this?

Ouch, you are doing it all wrong. First of all, you have to specify the starting page. If you want to download some data before navigating to it, you can create a special "download" page that is actually the first page navigated to when starting the application. And then, once the download is completed, you navigate to your main page. This is actually a replacement for the extended splash screen.
Also, never put while (true) in any UI code, that will simply freeze the application. Besides, if the application is frozen, you never get the chance to "unfreeze" it.

Related

How can I pop a navigation page when a value changes in a static class?

So I have a bit of a weird problem.
I've implemented a camera preview class (largely following this code here: https://learn.microsoft.com/en-us/samples/xamarin/xamarin-forms-samples/customrenderers-view/) but have added a button at the bottom to take a picture. This involves the use of both some xamarin forms code and some xamarin android code.
However, the CapturePage is only put on the stack when the user announces that they want to take a photo, and after the photo has been taken, I want to pop the Capture page to go back to the main screen. Currently, I have a static boolean value in the overall project that is changed from false to true when a capture has occurred. Is there some way to get my code in Main.xaml.cs to wait on this value changing, then pop whatever is on top of the navigation stack? Is this a use for a property changed? See code below:
The code in Project.Droid that handles the capturing and saving of the image:
void OnCapButtonClicked(object sender, EventArgs e)
{
// capButton.capture is an instance of Android.Hardware.Camera
capButton.capture.TakePicture(null, null, this);
// stop the preview when the capture happens
CameraInfoContainer.isPreviewing = false;
}
public void OnPictureTaken(byte[] data, Camera camera)
{
var filepath = string.Empty;
var clientInstanceId = Guid.NewGuid().ToString();
var saveLoc = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryPictures);
filepath = System.IO.Path.Combine(saveLoc.AbsolutePath, clientInstanceId + ".jpg");
try
{
System.IO.File.WriteAllBytes(filepath, data);
//mediascan adds the saved image into the gallery
var mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile);
mediaScanIntent.SetData(Android.Net.Uri.FromFile(new File(filepath)));
Forms.Context.SendBroadcast(mediaScanIntent);
}
catch (Exception e)
{
System.Console.WriteLine(e.ToString());
}
// CameraInfoContainer is a static class in Project NOT in Project.Droid
CameraInfoContainer.savedCapture = filepath;
CameraInfoContainer.capType = CaptureType.Photo;
CameraInfoContainer.captureComplete = true; // here is where I set the value (in Project)
}
Now the code in Project that pushes the capture page on the stack and that I want to trigger when the capture has happened:
// this method puts the capture page on the stack and starts the whole process
private async Task ExecuteNewCapture()
{
var cp = new CapturePage();
var np = new NavigationPage(cp);
await Navigation.PushModalAsync(np, true);
}
// this is the method that I want to trigger when a photo is taken (in Project/Main.xaml.cs)
private async Task Complete(string fileLoc)
{
await Navigation.PopModalAsync();
}
Answer is in a comment from Junior Jiang. Ended up using Xamarin.Forms.MessagingCenter to get done what I needed.

ManualResetEvent wait doesn't release after being set

I'm downloading two JSON files from the webs, after which I want to allow loading two pages, but not before. However, the ManualResetEvent that is required to be set in order to load the page never "fires". Even though I know that it gets set, WaitOne never returns.
Method that launches the downloads:
private void Application_Launching(object sender, LaunchingEventArgs e)
{
PhoneApplicationService.Current.State["doneList"] = new List<int>();
PhoneApplicationService.Current.State["manualResetEvent"] = new ManualResetEvent(false);
Helpers.DownloadAndStoreJsonObject<ArticleList>("http://arkad.tlth.se/api/get_posts/", "articleList");
Helpers.DownloadAndStoreJsonObject<CompanyList>("http://arkad.tlth.se/api/get_posts/?postType=webbkatalog", "catalog");
}
The downloading method, that sets the ManualResetEvent
public static void DownloadAndStoreJsonObject<T>(string url, string objName)
{
var webClient = new WebClient();
webClient.DownloadStringCompleted += (sender, e) =>
{
if (!string.IsNullOrEmpty(e.Result))
{
var obj = ProcessJson<T>(e.Result);
PhoneApplicationService.Current.State[objName] = obj;
var doneList = PhoneApplicationService.Current.State["doneList"] as List<int>;
doneList.Add(0);
if (doneList.Count == 2) // Two items loaded
{
(PhoneApplicationService.Current.State["manualResetEvent"] as ManualResetEvent).Set(); // Signal that it's done
}
}
};
webClient.DownloadStringAsync(new Uri(url));
}
The waiting method (constructor in this case)
public SenastePage()
{
InitializeComponent();
if ((PhoneApplicationService.Current.State["doneList"] as List<int>).Count < 2)
{
(PhoneApplicationService.Current.State["manualResetEvent"] as ManualResetEvent).WaitOne();
}
SenasteArticleList.ItemsSource = (PhoneApplicationService.Current.State["articleList"] as ArticleList).posts;
}
If I wait before trying to access that constructor, it easily passes the if-statement and doesn't get caught in the WaitOne, but if I call it immediately, I get stuck, and it never returns...
Any ideas?
Blocking the UI thread must be prevented at all costs. Especially when downloading data: don't forget that your application is executing on a phone, which has a very instable network. If the data takes two minutes to load, then the UI will be freezed for two minutes. It would be an awful user experience.
There's many ways to prevent that. For instance, you can keep the same logic but waiting in a background thread instead of the UI thread:
public SenastePage()
{
// Write the XAML of your page to display the loading animation per default
InitializeComponent();
Task.Factory.StartNew(LoadData);
}
private void LoadData()
{
((ManualResetEvent)PhoneApplicationService.Current.State["manualResetEvent"]).WaitOne();
Dispatcher.BeginInvoke(() =>
{
SenasteArticleList.ItemsSource = ((ArticleList)PhoneApplicationService.Current.State["articleList"]).posts;
// Hide the loading animation
}
}
That's just a quick and dirty way to reach the result you want. You could also rewrite your code using tasks, and using Task.WhenAll to trigger an action when they're all finished.
Perhaps there is a logic problem. In the SenastePage() constructor you are waiting for the set event only if the doneList count is less than two. However, you don't fire the set event until the doneList count is equal to two. You are listening for the set event before it can ever fire.

About Async Tasks and Disposal

In my MainWindow I have a button that can be used to open a Process (native OpenProcess call) and perform some checks on it's memory, but the method called on Click is asynchronous:
<Button Content="Attach" Click="OnClickAttach"/>
private async void OnClickAttach(Object sender, RoutedEventArgs e)
{
AttachmentResult result = await m_ViewModel.Attach();
switch (result)
// Different MessageBox depending on the result.
}
Now, let's see the ViewModel portion of code...
// MemoryProcess class is just a wrapper for Process' handle and memory regions.
private MemoryProcess m_MemoryProcess;
public async Task<AttachmentResult> Attach()
{
AttachmentResult result = AttachmentResult.Success;
MemoryProcess memoryProcess = NativeMethods.OpenProcess(m_SelectedBrowserInstance.Process);
if (memoryProcess == null)
result = AttachmentResult.FailProcessNotOpened;
else
{
Boolean check1 = false;
Boolean check2 = false;
foreach (MemoryRegion region in memoryProcess)
{
// I perform checks on Process' memory regions and I eventually change the value of check1 or check2...
await Task.Delay(1);
}
if (!check1 && !check2)
{
NativeMethods.CloseHandle(memoryProcess.Handle);
result = AttachmentResult.FailProcessNotValid;
}
else
{
// I keep the Process opened for further use. I save it to a private variable.
m_MemoryProcess = memoryProcess;
m_MemoryProcess.Check1 = check1;
m_MemoryProcess.Check2 = check2;
}
}
return result;
}
Now... here comes the problem. When the user closes the application, if a Process is opened, I must properly close its handle. So in my MainWindow I have the following code:
protected override void OnClosing(CancelEventArgs e)
{
m_ViewModel.Detach();
base.OnClosing(e);
}
And in my ViewModel I have the following code:
public void Detach()
{
if (m_MemoryProcess != null)
{
if (m_MemoryProcess.Check1)
// Do something...
if (m_MemoryProcess.Check2)
// Do something...
NativeMethods.CloseHandle(m_MemoryProcess.Handle);
m_MemoryProcess = null;
}
}
The Attach() method can take very long time, more than 2 minutes sometimes. I need to find a solution for the following issues:
If the user closes the application while Attach() method is running and before memoryProcess is saved to the private variable, the Process handle will not be closed.
If I save the MemoryProcess instance to the private variable just at the beginning of the Attach() method, there is a risk for the user to get a NullReferenceException if he closes the application while the Attach() method is processing its foreach loop.
I absolutely don't want to make the user wait for Attach() method to complete before letting him close the application. That's horrible.
How can I do this?
IMO, if you do not explicitly and specifically target to create separate detached/independent processes like, for example, through:
using PInvoke.CreateProcess
using
(new System.Management.ManagementClass("Win32_ProcessStartup"))
.Properties["CreateFlags"].Value = 8;
or maintaining child process alive upon app closing by launching them through separate shell scripts or other processes remaining to run after app closing;
creating a new thread in another independent process using CreateRemoteThread
etc.
or finding already run independently processes, you don't need to and probably should not "close" or dispose spawned by app processes. Windows (operting system) will close any unclosed spawned by app processes.
Also, I believe that it is impossible to execute any code in an application once it has started exiting or being closed.
PS (off-topic comment):
I do not even see that you close (really one should kill) or dispose your processes in your code...

Delay in event in code behind file

In my application I have a button to save some information. However, I would like to have a delay in the code before the last line is executed, so that the user could read the message that shows up before he gets redirected to the new page.
I know that doing this isn't at all an optimal way, but by some reasons (time, for example) I want to do it anyway.
So is it possible and if so, how could I do it?
Thanks in advance!
protected void SaveButton_Click(object sender, EventArgs e) {
// Lots of code not relevant for the problem here
Service service = new Service();
service.SaveMovie(movie);
successMessage.Visible = true;
happyMessage.Text = "The movie was successfully added, now add some genres!";
// Here I want a delay of 2 seconds before the next line is executed...
Response.Redirect(String.Format("~/Edit.aspx?id={0}", movie.MovieID), false);
}
You need to do this on the client side. One alternative is this:
Define a Javascript function in the page called redirect as so:
function redirect(url)
{
setTimeout(function(){window.location.href=url;} ,2000);
}
protected void SaveButton_Click(object sender, EventArgs e)
{
// Lots of code not relevant for the problem here
Service service = new Service();
service.SaveMovie(movie);
successMessage.Visible = true;
happyMessage.Text = "The movie was successfully added, now add some genres!";
// Here I want a delay of 2 seconds before the next line is executed...
ClientScript.RegisterStartupScript(this.GetType(),"somekey","redirect('"+String.Format("~/Edit.aspx?id={0}", movie.MovieID)+"');");
}
This will be easy if you are using Javascript. Use javascript will boost performance
Button_Click
{
string js ="<script type='text/javascript'>setTimeout(function()window.location.href="+String.Format("~/Edit.aspx?id={0}", movie.MovieID)+";} ,2000);</script>"
ScriptManager.RegisterStartupScript(Me.Page, GetType(Page), "js", js, False)
}
possible duplicate of
asp.net delay before response redirect

C# show gps location on map

I am working in a windows mobile application and I want to show my current location with google maps. I used the Location dll from the samples. As you see below in my code, I call the proper method for updating the map in the gps_Locationchanged event where I use the Invoke method to update the pictureboxe's image. The problem is that I can't use the main menu and the context menu of the application whenever i want. It's like they freeze until the new map finish downloading. Is there another way to do that in different thread so they can be used anytime?
void gps_LocationChanged(object sender, LocationChangedEventArgs args)
{
if (args.Position.LatitudeValid && args.Position.LongitudeValid)
{
pictureBox1.Invoke((UpdateMap)delegate()
{
center.Latitude = args.Position.Latitude;
center.Longitude = args.Position.Longitude;
LatLongToPixel(center);
image_request2(args.Position.Latitude, args.Position.Longitude);
});
}
}
Maybe something along these lines?
bool m_fetching;
void gps_LocationChanged(object sender, LocationChangedEventArgs args)
{
if (m_fetching) return;
if (args.Position.LatitudeValid && args.Position.LongitudeValid)
{
ThreadPool.QueueUserWorkItem(UpdateProc, args);
}
}
private void UpdateProc(object state)
{
m_fetching = true;
LocationChangedEventArgs args = (LocationChangedEventArgs)state;
try
{
// do this async
var image = image_request2(args.Position.Latitude, args.Position.Longitude);
// now that we have the image, do a synchronous call in the UI
pictureBox1.Invoke((UpdateMap)delegate()
{
center.Latitude = args.Position.Latitude;
center.Longitude = args.Position.Longitude;
LatLongToPixel(center);
image;
});
}
finally
{
m_fetching = false;
}
}
It's hard to say for sure, but it looks like the image_request2() method that (I assume) gets the actual image from the server is the problem. If you were to run this method on a worker thread, and provide a simple callback method that would paint the image on the screen once it's completely downloaded, this would leave your UI thread open to receive events from the user.

Categories