I am using a DevExpress MVC Pivot Grid and trying to work out some problems with the loading and saving of layouts. So far I have the following:
I have set my CustomActionRouteValues in the PivotGridSettings as follows:
CustomActionRouteValues = new { Controller = "Home", Action = "PivotGridCustomCallback" },
Which points to the following:
public ActionResult PivotGridCustomCallback(string action, string reportName)
{
if (string.IsNullOrEmpty(reportName))
{
reportName = "Report 1";
}
var settings = PivotGridLayoutHelper.DefaultPivotGridSettings;
if (action == "Save")
{
// TODO: Find a better solution than this. At the moment, if Save is called once, it is then called again every time the user changes the layout.. which is why we have the 'saved' variable here.
bool saved = false;
settings.AfterPerformCallback = (sender, e) =>
{
if (saved)
{
return;
}
SaveLayout(((MVCxPivotGrid)sender).SaveLayoutToString(), reportName);
saved = true;
};
}
else if (action == "Load")
{
// TODO: Find a better solution than this. At the moment, if Load is called once, it is then called again every time the user changes the layout.. which is why we have the 'loaded' variable here.
bool loaded = false;
string layoutString = LoadLayout(reportName);
if (!string.IsNullOrEmpty(layoutString))
{
settings.BeforeGetCallbackResult = (sender, e) =>
{
if (loaded)
{
return;
}
((MVCxPivotGrid)sender).LoadLayoutFromString(layoutString, PivotGridWebOptionsLayout.DefaultLayout);
loaded = true;
};
}
}
ViewBag.PivotSettings = settings;
return PartialView("PivotPartial");
}
The problem, as you can see in the code comments, is that after performing an action just one time, it then gets called EVERY time I make any sort of change. So, for example... say I load a report.. that's fine.. but then when I try expand something or add a field.. or do ANYTHING, nothing seems to happen on the UI.. and I figured out that's because immediately, this code gets called again:
settings.BeforeGetCallbackResult = (sender, e) =>
{
((MVCxPivotGrid)sender).LoadLayoutFromString(layoutString, PivotGridWebOptionsLayout.DefaultLayout);
};
That just keeps resetting the values to the saved layout, which means the UI looks like it's unresponsive when trying to change anything.
This is why I now have the boolean variable called loaded to check if it's already loaded. That works.. but it's an ugly hack.. because it's making unnecessary trips to the server each and every time the user does anything on the pivot grid.
Surely there must be a way to prevent these actions from firing all the time?
Related
I am working on an ASP.NET MVC web service. In a web page, when a user clicks on a button, this triggers a complex method that takes a bit of time to finish. I want to redirect the user to a waiting page and then, when the process is finished, to redirect the user to a new page.
When the process is done it raises an event, which I can listen to from the controller. But I cannot make the last step to work (the controller redirecting to the new page upon receiving the event).
Here is my very naïve attempt at doing it (with simpler names):
public MyController()
{
EventsControllerClass.ProcessComplete += new EventHandler<MyArgsClass>(OnEventReceived);
}
private void OnEventReceived(object sender, MyArgsClass eventArguments)
{
RedirectToPage();
}
private ActionResult RedirectToPage()
{
return RedirectToAction("PageName");
}
After many days working on this, I have a viable solution. It may not be pretty, but it works, and maybe some ideas can be useful for other people, so here it goes:
I will explain the solution to my particular problem: I need a button to redirect to a "waiting" page while a longer process runs in the background and raises an event when it is finished. When this event is received, we want to redirect the user (automatically) to a final page.
First, I created a class to listen to the event. I tried doing this directly in the controller, but you need to be careful about signing and unsigning, because apparently controllers get created and destroyed at each request. In this "listener class" I have a bool property that is set to "true" when the event is received.
When the first action is triggered, the controller normally redirects to the "wait" page, where I have this simple java script redirecting to the new action:
<script type="text/javascript">
window.location = "#Url.Action("WaitThenRedirect", "AuxiliaryControllerName")";
</script>
This sets in motion the long process (through another event). The key is that I do this with an asynchronous action (this controller inherits from AsyncController). (Note I used an auxiliary controller. This is to keep all asynchronous stuff apart.) This is how this looks (more info here):
public static event EventHandler<AuxiliaryEventsArgs> ProcessReady;
public void WaitThenRedirectAsync()
{
AsyncManager.OutstandingOperations.Increment();
ProcessReady += (sender, e) =>
{
AsyncManager.Parameters["success"] = e.success;
AsyncManager.OutstandingOperations.Decrement();
};
WaitForEvent();
}
public ActionResult WaitThenRedirectCompleted(bool success)
{
if (success)
{
return RedirectToAction("RedirectToView", "ControllerName");
}
else
{
return RedirectToAction("UnexpectedError", "ControllerName");
}
}
private void WaitForEvent()
{
bool isWaitSuccessful = true;
int waitingLoops = 0;
int waitingThreshold = 200;
int sleepPeriod = 100; // (milliseconds)
while (!EventsListener.IsTheThingReady())
{
System.Threading.Thread.Sleep(sleepPeriod);
++waitingLoops;
if (waitingLoops > waitingThreshold)
{
System.Diagnostics.Debug.WriteLine("Waiting timed out!");
isWaitSuccessful = false;
break;
}
}
isWaitSuccessful = true;
if (null != ProcessReady)
{
AuxiliaryEventsArgs arguments = new AuxiliaryEventsArgs();
arguments.success = isWaitSuccessful;
try
{
ProcessReady(null, arguments);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Error in event ProcessReady" + ex);
}
}
}
I believe it is possible to use ajax syntax for alternative solutions, but this is what I have and it works nicely. I believe this is not a very common need, but hopefully someone will benefit!
I've read several related posts and even posted and answer here but it seems like I was not able to solve the problem.
I have 3 Activities:
Act1 (main)
Act2
Act3
When going back and forth Act1->Act2 and Act2->Act1 I get no issues
When going Act2->Act3 I get no issues
When going Act3->Act2 I get occasional crashes with the following error: java.lang.IllegalStateException: trying to requery an already closed cursor android.database.sqlite.SQLiteCursor#.... This is a ListView cursor.
What I tried:
1. Adding stopManagingCursor(currentCursor);to the onPause() of Act2 so I stop managing the cursor when leaving Act2 to Act3
protected void onPause()
{
Log.i(getClass().getName() + ".onPause", "Hi!");
super.onPause();
saveState();
//Make sure you get rid of the cursor when leaving to another Activity
//Prevents: ...Unable to resume activity... trying to requery an already closed cursor
Cursor currentCursor = ((SimpleCursorAdapter)getListAdapter()).getCursor();
stopManagingCursor(currentCursor);
}
When returning back from Act3 to Act2 I do the following:
private void populateCompetitorsListView()
{
ListAdapter currentListAdapter = getListAdapter();
Cursor currentCursor = null;
Cursor tournamentStocksCursor = null;
if(currentListAdapter != null)
{
currentCursor = ((SimpleCursorAdapter)currentListAdapter).getCursor();
if(currentCursor != null)
{
//might be redundant, not sure
stopManagingCursor(currentCursor);
// Get all of the stocks from the database and create the item list
tournamentStocksCursor = mDbHelper.retrieveTrounamentStocks(mTournamentRowId);
((SimpleCursorAdapter)currentListAdapter).changeCursor(tournamentStocksCursor);
}
else
{
tournamentStocksCursor = mDbHelper.retrieveTrounamentStocks(mTournamentRowId);
}
}
else
{
tournamentStocksCursor = mDbHelper.retrieveTrounamentStocks(mTournamentRowId);
}
startManagingCursor(tournamentStocksCursor);
//Create an array to specify the fields we want to display in the list (only name)
String[] from = new String[] {StournamentConstants.TblStocks.COLUMN_NAME, StournamentConstants.TblTournamentsStocks.COLUMN_SCORE};
// and an array of the fields we want to bind those fields to (in this case just name)
int[] to = new int[]{R.id.competitor_name, R.id.competitor_score};
// Now create an array adapter and set it to display using our row
SimpleCursorAdapter tournamentStocks = new SimpleCursorAdapter(this, R.layout.competitor_row, tournamentStocksCursor, from, to);
//tournamentStocks.convertToString(tournamentStocksCursor);
setListAdapter(tournamentStocks);
}
So I make sure I invalidate the cursor and use a different one. I found out that when I go Act3->Act2 the system will sometimes use the same cursor for the List View and sometimes it will have a different one.
This is hard to debug and I was never able to catch a crashing system while debugging. I suspect this has to do with the time it takes to debug (long) and the time it takes to run the app (much shorter, no pause due to breakpoints).
In Act2 I use the following Intent and expect no result:
protected void onListItemClick(ListView l, View v, int position, long id)
{
super.onListItemClick(l, v, position, id);
Intent intent = new Intent(this, ActivityCompetitorDetails.class);
intent.putExtra(StournamentConstants.App.competitorId, id);
intent.putExtra(StournamentConstants.App.tournamentId, mTournamentRowId);
startActivity(intent);
}
Moving Act1->Act2 Act2->Act1 never gives me trouble. There I use startActivityForResult(intent, ACTIVITY_EDIT); and I am not sure - could this be the source of my trouble?
I would be grateful if anyone could shed some light on this subject. I am interested in learning some more about this subject.
Thanks,D.
I call this a 2 dimensional problem: two things were responsible for this crash:
1. I used startManagingCursor(mItemCursor); where I shouldn't have.
2. I forgot to initCursorAdapter() (for autocomplete) on onResume()
//#SuppressWarnings("deprecation")
private void initCursorAdapter()
{
mItemCursor = mDbHelper.getCompetitorsCursor("");
startManagingCursor(mItemCursor); //<= this is bad!
mCursorAdapter = new CompetitorAdapter(getApplicationContext(), mItemCursor);
initItemFilter();
}
Now it seems to work fine. I hope so...
Put this it may work for you:
#Override
protected void onRestart() {
// TODO Auto-generated method stub
super.onRestart();
orderCursor.requery();
}
This also works
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
startManagingCursor(Cursor);
}
I have a system whereby users can upload sometimes large(100-200 MB) files from within an MVC3 application. I would like to not block the UI while the file is uploading, and after some research, it looked like the new AsyncController might let me do what I'm trying to do. Problem is - every example I have seen isn't really doing the same thing, so I seem to be missing one crucial piece. After much futzing and fiddling, here's my current code:
public void CreateAsync(int CompanyId, FormCollection fc)
{
UserProfile up = new UserRepository().GetUserProfile(User.Identity.Name);
int companyId = CompanyId;
// make sure we got a file..
if (Request.Files.Count < 1)
{
RedirectToAction("Create");
}
HttpPostedFileBase hpf = Request.Files[0] as HttpPostedFileBase;
if (hpf.ContentLength > 0)
{
AsyncManager.OutstandingOperations.Increment();
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (o, e) =>
{
string fileName = hpf.FileName;
AsyncManager.Parameters["recipientId"] = up.id;
AsyncManager.Parameters["fileName"] = fileName;
};
worker.RunWorkerCompleted += (o, e) => { AsyncManager.OutstandingOperations.Decrement(); };
worker.RunWorkerAsync();
}
RedirectToAction("Uploading");
}
public void CreateCompleted(int recipientId, string fileName)
{
SystemMessage msg = new SystemMessage();
msg.IsRead = false;
msg.Message = "Your file " + fileName + " has finished uploading.";
msg.MessageTypeId = 1;
msg.RecipientId = recipientId;
msg.SendDate = DateTime.Now;
SystemMessageRepository.AddMessage(msg);
}
public ActionResult Uploading()
{
return View();
}
Now the idea here is to have the user submit the file, call the background process which will do a bunch of things (for testing purposes is just pulling the filename for now), while directing them to the Uploading view which simply says "your file is uploading...carry on and we'll notify you when it's ready". The CreateCompleted method is handling that notification by inserting a message into the users's message queue.
So the problem is, I never get the Uploading view. Instead I get a blank Create view. I can't figure out why. Is it because the CreateCompleted method is getting called which shows the Create view? Why would it do that if it's returning void? I just want it to execute silently in the background, insert a message and stop.
So is this the right approach to take at ALL? my whole reason for doing it is with some network speeds, it can take 30 minutes to upload a file and in its current version, it blocks the entire application until it's complete. I'd rather not use something like a popup window if I can avoid it, since that gets into a bunch of support issues with popup-blocking scripts, etc.
Anyway - I am out of ideas. Suggestions? Help? Alternate methods I might consider?
Thanks in advance.
You are doing it all wrong here. Assume that your action name is Create.
CreateAsync will catch the request and should be a void method and returns nothing. If you have attributes, you should apply them to this method.
CreateCompleted is your method which you should treat as a standard controller action method and you should return your ActionResult inside this method.
Here is a simple example for you:
[HttpPost]
public void CreateAsync(int id) {
AsyncManager.OutstandingOperations.Increment();
var task = Task<double>.Factory.StartNew(() => {
double foo = 0;
for(var i = 0;i < 1000; i++) {
foo += Math.Sqrt(i);
}
return foo;
}).ContinueWith(t => {
if (!t.IsFaulted) {
AsyncManager.Parameters["headers1"] = t.Result;
}
else if (t.IsFaulted && t.Exception != null) {
AsyncManager.Parameters["error"] = t.Exception;
}
AsyncManager.OutstandingOperations.Decrement();
});
}
public ActionResult CreateCompleted(double headers1, Exception error) {
if(error != null)
throw error;
//Do what you need to do here
return RedirectToAction("Index");
}
Also keep in mind that this method will still block the till the operation is completed. This is not a "fire and forget" type async operation.
For more info, have a look:
Using an Asynchronous Controller in ASP.NET MVC
Edit
What you want here is something like the below code. Forget about all the AsyncController stuff and this is your create action post method:
[HttpPost]
public ActionResult About() {
Task.Factory.StartNew(() => {
System.Threading.Thread.Sleep(10000);
if (!System.IO.Directory.Exists(Server.MapPath("~/FooBar")))
System.IO.Directory.CreateDirectory(Server.MapPath("~/FooBar"));
System.IO.File.Create(Server.MapPath("~/FooBar/foo.txt"));
});
return RedirectToAction("Index");
}
Notice that I waited 10 seconds there in order to make it real. After you make the post, you will see the it will return immediately without waiting. Then, open up the root folder of you app and watch. You will notice that a folder and file will be created after 10 seconds.
But (a big one), here, there is no exception handling, a logic how to notify user, etc.
If I were you, I would look at a different approach here or make the user suffer and wait.
I'm writing a command line application that checks the SPFieldCollection returned by the SPWeb.Fields property, but it's not behaving as I'd like. I have hundreds of SPWebs and it's definitely touching them all, but for all but the initial SPWeb, it's returning an empty Fields property. What am I doing wrong?
string siteUrl = "http://webroot/sitecoll";
using (SPSite siteCol = new SPSite(siteUrl))
{
using(SPWeb outerWeb = siteCol.OpenWeb())
{
foreach (SPWeb innerWeb in siteCol.AllWebs)
{
LogMessageToFile(String.Format("Checking {0}", innerWeb.Url)); //executed for each of the hundreds of innerWebs
if (innerWeb.Fields.ContainsField("Year"))
{
// Never accessed after the first time through because innerWeb.Fields is empty
}
}
}
}
SPweb.Fields live at the site collection level.
Unless you specifically create the fields at the subsite levels you will get 0 returned.
I have written an add in that takes the active document as a parameter. So each time that the active document has changed, I need to know. To do so, I wanted to use "Events.DocumentEvents.DocumentOpened" event of the DTE2 object. But the problem is that event is never get fired even though I change the active document.
The code snippet is as follows
public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
{
_applicationObject = (DTE2)application;
_applicationObject.Events.DocumentEvents.DocumentOpened += new _dispDocumentEvents_DocumentOpenedEventHandler(DocumentEvents_DocumentOpened);
...
}
void DocumentEvents_DocumentOpened(Document Document)
{
MessageBox.Show("Not called");
}
I have tried with DocumentEvents as well but no success. Any ideas?
I had just realized that I focused on the wrong event and thats why it was not fired. With the code below I got what I intended to. So instead of DocumentEvents, I had to use WindowEvents.
....
_applicationObject.Events.WindowEvents.WindowActivated += new _dispWindowEvents_WindowActivatedEventHandler(WindowEvents_WindowActivated);
}
void WindowEvents_WindowActivated(Window GotFocus, Window LostFocus)
{
if (ucCAST != null && GotFocus.Document != null)
((CAST)ucCAST).refreshCode(GotFocus.Document.Name);
}