Is there a way to check in the code in a form if the form is being used as a caller for another form that is currently open?
As far as I know, out of the box there is no way to check this. If you have a small set of specific forms (say one caller form and 2 or 3 called forms), it should be pretty easy to manage a list of called forms in the caller form by calling a method in the caller form from the called forms.
If you want a generic solution that works for all forms, things get interesting. You probably want to take a look at the SysSetupForm[Run] and SysForm[Run] classes and try to extend these with a similar called forms management logic. It may be necessary to store the data of the called forms management in the global cache.
That said I suggest you rethink your form design. Usually such a requirement is a result of bad design.
This is just an idea, which I haven't tried but I don't see why it wouldn't work. I didn't optimise the code, so use this idea just as an inspiration.
First, in each form being called from the main form modify the init method as follows:
public void init()
{
super();
blahblah::addHandleToCache(element.hWnd());
}
This is the code of blahblah::addHandleToCache:
public static client void addHandleToCache(int _handle)
{
#define.CACHE_OWNER('MyCallerForm')
container con;
int hWnd;
int i;
//get a container with open window handles from the cache
if (infolog.globalCache().isSet(#CACHE_OWNER, curUserId()))
{
con = infolog.globalCache().get(#CACHE_OWNER, curUserId());
}
//remove handles of closed windows from the container
for (i = conLen(con); i >= 1; i--)
{
hWnd = conPeek(con, i);
if (!WinApi::isWindow(hWnd))
{
conDel(con, i, 1);
}
}
//add the current window handle to the container
con += _handle;
//save the container in the cache
infolog.globalCache().set(#CACHE_OWNER, curUserId(), con);
}
Now when you want to check whether your main form is being used as a caller for other open forms you can use the following check:
public boolean hasOpenChildForms()
{
#define.CACHE_OWNER('MyCallerForm')
container con;
int hWnd;
boolean ret;
//get a container with open window handles from the cache
if (infolog.globalCache().isSet(#CACHE_OWNER, curUserId()))
{
con = infolog.globalCache().get(#CACHE_OWNER, curUserId());
}
for (i = conLen(con); i >= 1; i--)
{
hWnd = conPeek(con, i);
if (WinApi::isWindow(hWnd))
{
//the form is open
ret = true
}
else
{
//remove handles of closed windows from the container
conDel(con, i, 1);
}
}
return ret;
}
Yes, there is.
Your code should look something like this for AX 2009:
Object caller = element.args().caller();
if (SysDictClass::isEqualOrSuperclass(classIdGet(caller), classNum(FormRun)))
{
// ...
}
And something like this for AX 2012:
Object caller = element.args().caller();
if (caller is FormRun)
{
// ...
}
But please note that the caller() will only be set at this point if the form was called through a menu item. If you invoke a method directly, you'll have to manually set the caller() in the Args object.
If you want to call a method on the caller form, I've blogged about this in the past:
http://devexpp.blogspot.com.br/2013/09/calling-methods-on-caller-dynamics-ax.html
Related
We are using Workflow Foundation 4 to implement custom logic in our application. One particular thing is that we are using variables of a custom type that are associated with a ressource in an external system.
When such a variable is no longer in use in a workflow, I would like to dispose of the corresponding resource in the external system.
How can my custom host be notified at runtime that my variable goes out of scope and/or is disposed. Do I need my variable objects to derive from a particular class or interface ? Do I need to inject a particular extension in the workflow instance ?
One way could be to implement a custom TrackingParticipant. This can be used to watch for when an activity's state changes to a closed state. When it is closed, you can inspect the arguments to see if any are of a resource that you'd like to clean up.
It could look something like this:
public interface IResource
{
}
public class MyTrackingParticipant : TrackingParticipant
{
private readonly MyResourceManager resourceManager;
public MyTrackingParticipant(MyResourceManager resourceManager)
{
this.resourceManager = resourceManager;
}
protected override void Track(TrackingRecord record, TimeSpan timeout)
{
var activityStateRecord = record as ActivityStateRecord;
if (activityStateRecord != null && activityStateRecord.State == ActivityStates.Closed)
{
// Scan arguments to see if resources should be deallocated from resource manager.
foreach (var keyValuePair in activityStateRecord.Arguments)
{
// If the argument is of a resource type...
var resource = keyValuePair.Value as IResource;
if (resource != null)
this.resourceManager.DeallocateResource(resource);
}
}
}
}
And using the custom tracking participant is just like any other WF extension:
var resourceManager = new MyResourceManager();
var wfResourceTrackingParticipant = new MyTrackingParticipant(resourceManager);
var workflow1 = new Workflow1();
var workflowApp = new WorkflowApplication(workflow1);
workflowApp.Extensions.Add(wfResourceTrackingParticipant);
I'm trying to show window when user need to be notify about some work to do. Every think work fine, but i want to show form absolute topmost. I set form property TopMost = true but it does not work, window still show behind other forms.
I figure out that TopMost = true don't work only with BackgroundWorker, when i use Timer class it work fine. I'm wonder why? Anybody can explain me this?
Here is simple example what i want to do.
static void Main(string[] args)
{
try
{
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerAsync();
Application.Run(new Form());
}
catch (Exception exp)
{
Console.WriteLine(exp);
}
}
static void worker_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
System.Threading.Thread.Sleep(1000);
if (NeedNotify())
{
NotifyForm myNotifyForm = new NotifyForm();
myNotifyForm.TopMost = true;
myNotifyForm.ShowDialog(); // NotifyForm still show behind others windows
}
}
}
private static bool NeedNotify()
{
return true;
}
}
Creating the form within the background worker causes the form to be created on a different thread. Instead, create and show the form in your main thread before calling RunWorkerAsync.
Another problem may arise from the fact that you're creating the "notification" before the application's main loop is even started. You may consider reorganizing your code so that the background worker is started from the main form's OnLoad event.
I have a method where I READ objects from DB, for instance:
public Object getProduct(int categoryId, int productId)
{
DataClassesDataContext db = new DataClassesDataContext(Settings.getDefaultConnectionStringName());
switch (categoryId)
{
case CCategorii.CARTI_ID:
{
IEnumerable<Carti> product = (from c in db.Cartis
where c.Carti_id == productId
&& c.Vizibil == true
select c);
if (product.Count() != 0)
return product.First();
break;
}
//so on
}
}
Now I have another method where I do the update:
public void updateProduct()
{
Object productToBeUpdated = getProduct(1,1);
DataClassesDataContext db = new DataClassesDataContext(Settings.getDefaultConnectionStringName());
//update some properties of the product
productToBeUpdated.setQuantity(productToBeUpdated.getQuantity()+1);
db.submitChanges();
}
Well, the product was succcesfully read from previous method but changes were not done into the DB.
I think the cause is that I do this READ-UPDATE in two different DataContext...If this is the cause how do you threat this situations?
Oh yeah, I can read the product and update in the same method but this means to duplicate the method I use for reading and add to it update stuff... and I would like to avoid this.
I would assume it's because you are using a different context for the read and write. Try moving your DataClassesDataContext variable to class level.
One option is: use a common data context, and pass it to your getXXX methods as a parameter:
public Object getProduct(DataClassesDataContext db, int categoryId, int productId)
{
switch (categoryId)
{
case CCategorii.CARTI_ID:
{
IEnumerable<Carti> product = (from c in db.Cartis
where c.Carti_id == productId
&& c.Vizibil == true
select c);
if (product.Count() != 0)
return product.First();
break;
}
//so on
}
}
and then:
public void updateProduct()
{
using (DataClassesDataContext db = new DataClassesDataContext(Settings.getDefaultConnectionStringName()))
{
Object productToBeUpdated = getProduct(db, 1,1);
//update some properties of the product
productToBeUpdated.setQuantity(productToBeUpdated.getQuantity()+1); // THX #AVD, didn't notice that.
db.submitChanges();
}
}
You are using two different instances of your DataContext.
When implementing a web app, the best option is usually to align the lifetime of your DataContext to the lifetime of one http request. The lifetime you use is just too short.
Another option is to attach the object to the write DataContext:
db.Cartis.Attach(yourReadObject);
updateProperties(yourReadObject);
db.submitChanges();
EDIT
Ok, you have to detach the object from your other context first. See this article on how to do it.
But i really would recommend to use a single DataContext object and extend the lifetime to the httprequest scope.
This can be done really nice with an ioc container like autofac.
You can't use ++ operator and use the same context to update an object. Try this,
productToBeUpdated.setQuantity(productToBeUpdated.getQuantity()+1);
As soon as your DataContext goes out of scope your entity becomes detached from it. That means it's no longer being tracked by your Context and it can't save the changes you make to it.
You could share the context so the entity doesn't get detached from your context or you could reattach it to the second context (DataContext.Attach)
In my DAL, I'm currently using this in a base class:
protected static MyCMSEntities MyCMSDb
{
get { return new MyCMSEntities(ConfigurationManager.ConnectionStrings["MyCMSEntities"].ConnectionString); }
}
and calling like this from a subclass:
public static bool Add(ContentFAQ newContent)
{
MyCMSEntities db = MyCMSDb;
newContent.DateModified = DateTime.Now;
newContent.OwnerUserId = LoginManager.CurrentUser.Id;
db.ContentFAQ.AddObject(newContent);
return db.SaveChanges() > 0;
}
I understand the method to get the context is static, but as it creates a new intance of the context, this is not static, i.e. it is new for each call to the Add method.
Am I correct and more importantly, ok for a web application?
Thanks.
You are correct in using a new context for every web call - but why this obfuscation? I would recommend removing this indirection with the static property (makes the code harder to understand) and also using a using block since the context is disposable:
public static bool Add(ContentFAQ newContent)
{
using(var db = new MyCMSEntities())
{
newContent.DateModified = DateTime.Now;
newContent.OwnerUserId = LoginManager.CurrentUser.Id;
db.ContentFAQ.AddObject(newContent);
return db.SaveChanges() > 0;
}
}
Also the default constructor of the context should use the default connection string, which is the right one if you didn't change it in your configuration (otherwise just add it back in).
I'm using Model-View-Presenter framework. When Loading a page, I'm having trouble setting the selected item that came from the Database.
In view, I know I need:
protected void ddlStatus_SelectedIndexChanged(object sender, EventArgs e)
{
presenter.DdlStatusSelectedIndexChanged();
// what should this pass?
}
Then in Presenter:
public void DdlStatusSelectedIndexChanged()
{
view.DdlStatus = ???
// Should I pass the SelectedIndex?
}
I also think that part of my problem is that DdlStatus I have as a List.
Interface:
List<StatusDTO> DdlStatus { set; get; }
Does anybody have some simple examples of this?
The best I found is here (but needs formatted!) --->
http://codebetter.com/blogs/jeremy.miller/archive/2006/02/01/137457.aspx
Thanks!
Which framework are you using? The typical way the presenter/view relationship works is through events; the view defines events that the presenter attaches to, to receive those state change notifications. There are other options too.
Your model should contain the list of statuses and the selected status. Depending on the "flavor" of MVP, you would either have the presenter call a property on the view to pass it the selected index, and your view would pass it to the control, or the view takes the index from the model directly.
HTH.
I figured this out. It's a bit of a cheese but ...
public int DdlStatusSelectedIndex
{
set
{
for (int i = 0; i < ddlStatus.Items.Count; i++)
{
if (ddlStatus.Items[i].Value.Equals(value.ToString()))
{
ddlStatus.SelectedIndex = value;
}
}
}
}