WPF Memory Leak from Drag-Drop - wpf

Using Red-Gate tools we have detected that the System.Windows.DataObject is holding a reference to a dragObject (a framework element) that is hanging around from an operation long since completed.
How does one "clear" the drag object once DragDrop.DoDragDrop? Is there a way to pass a null through this and have it fall right through?

I just discovered this gem myself, my solution was to use a WeakReference to the data item being dragged.
DataObject data = new DataObject(new WeakReference(this.draggedData));
DragDrop.DoDragDrop((DependencyObject)sender, data, DragDropEffects.Move);
and then in the drop
var draggedItem = e.Data.GetData(this.format.Name) as WeakReference;
if (draggedItem != null && draggedItem.IsAlive)
{
....
}

First of all a big thanks to Ian Oakes for his solution. I needed a slight variant however: I had to make sure that dropping always works, even if the garbage collector ran in the meanwhile. Here is the solution:
public partial class DragDropDemo : Window
{
private SomeDragDropData _dragDropData;
private void OnMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
_dragDropData = new SomeDragDropData { Text = "Some drag data" };
var dataObject = new DataObject("SomeObjectTypeId", new WeakReference<SomeDragDropData>(_dragDropData));
DragDrop.DoDragDrop((DependencyObject)sender, dataObject, DragDropEffects.Move);
_dragDropData = null;
}
}
private void OnDrop(object sender, DragEventArgs e)
{
var weakReferenceData = e.Data.GetData("SomeObjectTypeId") as WeakReference<SomeDragDropData>;
if (weakReferenceData != null && weakReferenceData.IsAlive)
MessageBox.Show(weakReferenceData.Target.Text);
}
}
public class SomeDragDropData
{
public string Text;
}
Some remarks:
The reason this works is because DoDragDrop blocks until the user triggered the drop operation. Hence, _dragDropData is made null only after the drag-drop operation is fully finished.
It is very important to make _dragDropData a member variable. Merely making it a local variable is not enough: when the garbage collector is triggered the object might get disposed. This results in a very hard to reproduce bug because it is not because the garbage collector is triggered that the object necessarily gets cleaned up. From what I saw it only gets cleaned up when a lot of memory got allocated and deallocated

Related

Interact with the Ok/Accept Or Cancel Button of a RepositoryItemTimeSpanEdit?

as seen in this post, I need to interact with the button, I mean, save the value of the repository when the user press the OK button, any suggest?
You need to find your TimeSpanEdit control inside of the popup form. You can iterate through popupForm.Controls collection to find out the control with TimeSpanEdit type. Here is example of how to do it. After that you can use TimeSpanEdit.TimeSpan property to get the value of TimeSpanEdit control.
private void OkButton_Click(object sender, EventArgs e)
{
var popupForm = (TimeSpanEditDropDownForm)OwnedForms.FirstOrDefault(item => item is TimeSpanEditDropDownForm);
if (popupForm == null)
return;
var timeSpanEdit = GetAll(this, typeof(TimeSpanEdit)).FirstOrDefault();
if (timeSpanEdit == null)
return;
MessageBox.Show(timeSpanEdit.TimeSpan.ToString());
}
public IEnumerable<Control> GetAll(Control control,Type type)
{
var controls = control.Controls.Cast<Control>();
return controls.SelectMany(ctrl => GetAll(ctrl,type))
.Concat(controls)
.Where(c => c.GetType() == type);
}
I think you can use object sender. sender will contains probably TimeSpanEditDropDownForm and there you should get actual value of this form. :)
I presume this code is called from controller is it?
if it is true than you have View.CurrentObject and you must know which property uses this TimeSpanEditDropDownForm so you could do something like this.
private void OkButton_Click(object sender, EventArgs e)
{
MyClass myClass = View.CUrrentObject as MyClass;
TimeSpanEditDropDownForm timeSpanForm = sender as TimeSpanEditDropDownForm;
myClass.CurrentTime = timeSpanForm.CurrentTime;
myClass.Session.CommitChanges();
MessageBox.Show("Ok");
}
I dont know what is name of right attribute wich store TimeSpan inside TimeSpanEditDropDownForm thats thing you must find out but I think it could helps :)

Silverlight Asynchronous operation Foreach loop

I have a query for getting a list of prescriptions as below:
var PRSCRPTSQuery = GV.dbContext.Load(GV.dbContext.GetPRSCRPTQuery(GV.curCustomer.CustCode,
oOrdritemEdited.ProdCode, oOrdritemEdited.MedCode));
PRSCRPTSQuery.Completed += new EventHandler(PRSCRPTSQuery_Completed);
In the query completed event, I have the following code :
void PRSCRPTSQuery_Completed(object sender, EventArgs e)
{
lstPRSCRPT = GV.dbContext.PRSCRPTs.Where(p=>p.Status =="Activated").ToList();
if (lstPRSCRPT.Count > 0)
{
foreach (var rec in lstPRSCRPT)
{
var OrderItemQuery = GV.dbContext.Load(GV.dbContext.GetOrdritemsQuery(rec.PresNo));
OrderItemQuery.Completed += new EventHandler(OrderItemQuery_Completed);
}
}
}
The list lstPRSCRPT can contain more than one record. I presume, the foreach loop will advance to the next item in the loop without waiting for the OrderItemQuery_Completed event which is below:
void OrderItemQuery_Completed(object sender, EventArgs e)
{
lstOrderItem = GV.dbContext.OrderItems.ToList();
if (lstOrderItem.Count > 0)
{
foreach (var OrdrItemRec in lstOrderItem)
{
TotTonnes = (double)(TotTonnes + OrdrItemRec.Quantity);
}
}
}
Is there any work around for this situation? I am new to the asynchronous type of programming in SL
I see where your coming from and when i first started Silverlight programming i gripped to my preconceptions of synchronous execution so i know what i have when ive finished calling a query and also i know exactly where it's errored.
Silverlight however takes this concept and tries to rip it from you yelling "This way is better trust me!" and for the purpose it serves of enriching client side interactivity it certainly succeeds. It just takes time. You just need to learn more about the style of how to link it all together.
The link previously shown by Faster Solutions shows where C# is going in terms of asynchronous coding but it pays to know what its actually accomplishing for you. Some of which you've already grasped in the code you've linked in the question.
When i've faced the same situation you have where you have back to back async callbacks is to raise an event when i've finished doing what i'm doing. For example:
public event EventHandler<EventArgs> LoadComplete;
public int QueryCount {get;set;}
public int QuerysCompleted {get;set;}
public void GetItems()
{
var PRSCRPTSQuery = GV.dbContext.Load(GV.dbContext.GetPRSCRPTQuery
(GV.curCustomer.CustCode, oOrdritemEdited.ProdCode, oOrdritemEdited.MedCode));
PRSCRPTSQuery.Completed += new EventHandler(PRSCRPTSQuery_Completed);
LoadComplete += loader_LoadComplete;
}
void PRSCRPTSQuery_Completed(object sender, EventArgs e)
{
lstPRSCRPT = GV.dbContext.PRSCRPTs.Where(p=>p.Status =="Activated").ToList();
if (lstPRSCRPT.Count > 0)
{
QueryCount = lstPRSCRPT.Count;
foreach (var rec in lstPRSCRPT)
{
var OrderItemQuery = GV.dbContext.Load(GV.dbContext.GetOrdritemsQuery(rec.PresNo));
OrderItemQuery.Completed += new EventHandler(OrderItemQuery_Completed);
}
}
}
void OrderItemQuery_Completed(object sender, EventArgs e)
{
QueryCompleted++;
lstOrderItem = GV.dbContext.OrderItems.ToList();
if (lstOrderItem.Count > 0)
{
foreach (var OrdrItemRec in lstOrderItem)
{
TotTonnes = (double)(TotTonnes + OrdrItemRec.Quantity);
}
}
if(QueryCompleted == QueryCount)
{
RaiseLoadComplete();
}
}
public void RaiseLoadComplete()
{
if(LoadComplete != null)
{
LoadComplete(this, new EventArgs());
}
}
void loader_LoadComplete(object sender, EventArgs e)
{
//Code to execute here
}
I attach an event when starting the first query of what code to execute when i'm done. In the first query call back i initialise a count of how many responses i am expecting. Then in the second query callback i increment until i get the right amount and call the event to say im done.
The only caution with this approach is if one of the queries error, The final code will never get executed.
You might find that the VS Async CTP of interest. It introduces the new "async" keyword for handling asyncronous events. He's a blog explaining it: VS Async

Diffrernce between BackgroundWorker.ReportProgress() and Control.BeginInvoke()

What is the difference between options 1 and 2 in the following?
private void BGW_DoWork(object sender, DoWorkEventArgs e)
{
for (int i=1; i<=100; i++)
{
string txt = i.ToString();
if (Test_Check.Checked)
//OPTION 1
Test_BackgroundWorker.ReportProgress(i, txt);
else
//OPTION 2
this.BeginInvoke((Action<int, string>)UpdateGUI,
new object[] {i, txt});
}
}
private void BGW_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
UpdateGUI(e.ProgressPercentage, (string)e.UserState);
}
private void UpdateGUI(int percent, string txt)
{
Test_ProgressBar.Value = percent;
Test_RichTextBox.AppendText(txt + Environment.NewLine);
}
Looking at reflector, the Control.BeginInvoke() appears to use:
this.FindMarshalingControl().MarshaledInvoke(this, method, args, 1);
Which seems to eventually call some native functions like PostMessage(), couldn't exactly figure out the flow from reflector (pesky compiler goto optimizations)
Whereas BackgroundWorker.Invoke() appears to use:
this.asyncOperation.Post(this.progressReporter, args);
Which seems to eventually call ThreadPool.QueueUserWorkItem()
(I'm just guessing these are the relevant function calls for each case.) If I understand correctly, using the ThreadPool would not guarantee execution order whereas using the Post mechanism would. Perhaps that would be a potential difference ? (EDIT - I couldn't synthesize such a situation - call order seems to be preserved in both cases, at least in my simple tests.)
Thanks!
They are both the same. The call you're seeing in BackgroundWorker uses SynchronizationContext. Indeed the default implementation of the Post() method uses the thread pool, but when starting a Windows Forms app, the default synchronization context is replaced by WindowsFormsSynchronizationContext, which actually calls Control.BeginInvoke().
One big difference is that Control.Invoke will block until the UpdateGUI call has been executed and completed, whereas BackgroundWorker.ReportProgress will not block (it will return immediately, before the BackgroundWorker raises the event).
If you want them to behave the same, call Control.BeginInvoke (which doesn't block) instead.
I've found a significant difference. Closing the form while the BGW is running will cause this.Invoke() and this.BeginInvoke() to throw an ObjectDisposedException. The BGW ReportProgress mechanism circumvents that. To enjoy the best of both worlds, the following pattern works nicely
public partial class MyForm : Form
{
private void InvokeViaBgw(Action action)
{
Packing_Worker.ReportProgress(0, action);
}
private void BGW_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (this.IsDisposed) return; //You are on the UI thread now, so no race condition
var action = (Action)e.UserState;
action();
}
private private void BGW_DoWork(object sender, DoWorkEventArgs e)
{
//Sample usage:
this.InvokeViaBgw(() => MyTextBox.Text = "Foo");
}
}

Winforms StatusStrip - why are there periods where it is blank when I'm updating it?

BACKGROUND: I have a WindowForms v3.5 application with a StatusStrip set to be used as a TooStripStatusLabel. I'm issues quite a lot of updates to it during a task that is running, however there are noticable periods where it is BLANK. There are no points when I am writing a blank to the status strip label either.
QUESTION: Any ideas why I would be seeing period where the status strip label is blank, when I don't expect it to be?
How I update it:
private void UpdateStatusStrip(string text)
{
toolStripStatusLabel1.Text = text;
toolStripStatusLabel1.Invalidate();
this.Update();
}
PS. Calling Application.DoEvents() after the this.Update() does not seem to help. I actually am calling this via the backgroundworker control, so:
(a) I start up the background worker:
private void Sync_Button_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
DisableUpdateButtons();
}
(b) the background worker calls updates:
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
backgroundWorker1.ReportProgress(1, "Example string");
MainForm.MyC.SyncFiles(sender);
}
(c) The MyC business class uses it too, e.g.
public void SyncFiles(object sender)
{
BackgroundWorker bgw = (System.ComponentModel.BackgroundWorker) sender;
bgw.ReportProgress(1, "Starting sync...");
.
.
.
}
(d) This event picks it up:
private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
UpdateStatusStrip((string)e.UserState);
}
(e) And again the update status strip
private void UpdateStatusStrip(string text)
{
toolStripStatusLabel1.Text = text;
toolStripStatusLabel1.Invalidate();
this.Update();
}
Does this help?
The reason is possibly in the caller of this function. If you call it from another thread, use Control.BeginInvoke instead of direct call. If you call it from the main application thread during long processing, try Application.DoEvents after UpdateStatusStrip call.

2 Issues with BackgroundWorker component

Firstly, I know I should be using proper Threading techniques (Threadpool, BeginInvoke, etc.) to accomplish this, but thats a bit over my head currently and will call for some time to read over material and understand it (if you have any URL references for my scenario, please feel free to post it).
In the interim I am using the backgroundWorker to pull a very large resultset and populate a DatagridView with it. I successfully create a SortableBindingList<TEntities> in my DoWork event and pass that out in the result. And in the RunWorkerCompleted event, I cast and bind that SortableBindingList<TEntities> to my Grid. My 2 main areas of concern are as follows:
1) Access to private variables.
I want to pass one of two parameters List<long> into my DoWork event, but run a different query depending on which list was passed to it. I can get around this by declaring a class-level private boolean variable that acts a flag of sorts. This seems silly to ask, but in my DoWork, am I allowed to access that private variable and route the query accordingly? (I've tested this and it does work, without any errors popping up)
private bool SearchEngaged = false;
private void bgw_DoWork(object sender, DoWorkEventArgs e) {
BackgroundWorker worker = sender as BackgroundWorker;
e.Result = GetTasks((List<long>)e.Argument, worker, e);
}
SortableBindingList<Task> GetTasks(List<long> argsList, BackgroundWorker worker, DoWorkEventArgs e) {
SortableBindingList<Task> sbl = null;
if (worker.CancellationPending) {
e.Cancel = true;
}
else {
if (SearchEngaged) {
sbl = DU.GetTasksByKeys(argsList);
}
else {
sbl = DU.GetTasksByDivision(argsList);
}
}
return sbl;
}
2) UI Thread freezes on beginning of RunWorkerCompleted.
Ok, I know that my UI is responsive during the DoWork event, 'cos it takes +/- 2seconds to run and return my SortableBindingList<Task> if I don't bind the List to the Grid, but merely populate it. However my UI freezes when I bind that to the Grid, which I am doing in the RunWorkerCompleted event. Keep in mind that my Grid has 4 image columns which I handle in CellFormatting. This process takes an additional 8 seconds to accomplish, during which, my UI is completely non-interactive. Im aware of the cross-thread implications of doing so, but is there any way I can accomplish the Grid population and formatting either in the background or without causing my UI to freeze? RunWorkeCompleted looks like so:
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
if (e.Cancelled) {
lblStatus.Text = "Operation was cancelled";
}
else if (e.Error != null) {
lblStatus.Text = string.Format("Error: {0}", e.Error.Message);
}
else {
SortableBindingList<Task> sblResult = (SortableBindingList<Task>)e.Result;
dgv.DataSource = sblResult;
dgv.Enabled = true;
TimeSpan Duration = DateTime.Now.TimeOfDay - DurationStart;
lblStatus.Text = string.Format("Displaying {0} {1}", sblResult.Count, "Tasks");
lblDuration.Visible = true;
lblDuration.Text = string.Format("(data retrieved in {0} seconds)", Math.Round(Duration.TotalSeconds, 2));
cmdAsyncCancel.Visible = false;
tmrProgressUpdate.Stop();
tmrProgressUpdate.Enabled = false;
pbStatus.Visible = false;
}
}
Sorry for the lengthy query, but I will truly appreciate your responses! thank you!
Your code appears to be doing exactly the right thing.
As for the 8 seconds that it takes for the UI thread to update the screen, there's not much you can do about that. See my answer to this question.
To optimise the UI part, you could try calling SuspendLayout and ResumeLayout on the grid or its containing panel.
You could also look at trying to reduce the amount of processing that is done during the data binding. For example:
Calculations done in the grid could be moved into the data model (thereby doing them in the worker thread).
If the grid auto-calculates its columns based on the data model, then try hard-coding them instead.
EDIT: Page the data in the Business Layer and make the grid only show a small number of rows at a time.
I think the easiest solution for your problem is setting the datasource of your grid in DoWork instead of RunWorkerCompleted using Dispatcher.BeginInvoke which you have mentioned yourself. Something like this:
private bool SearchEngaged = false;
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
SortableBindingList<Task> sblResult = GetTasks((List<long>)e.Argument, worker, e);
BeginInvoke((Action<object>)(o => dataGridView1.DataSource = o), sblResult);
}
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled) {
lblStatus.Text = "Operation was cancelled";
}
else if (e.Error != null) {
lblStatus.Text = string.Format("Error: {0}", e.Error.Message);
}
else
{
dgv.Enabled = true;
TimeSpan Duration = DateTime.Now.TimeOfDay - DurationStart;
lblStatus.Text = string.Format("Displaying {0} {1}", sblResult.Count, "Tasks");
lblDuration.Visible = true;
lblDuration.Text = string.Format("(data retrieved in {0} seconds)", Math.Round(Duration.TotalSeconds, 2));
cmdAsyncCancel.Visible = false;
tmrProgressUpdate.Stop();
tmrProgressUpdate.Enabled = false;
pbStatus.Visible = false;
}
}
As far as the private variable issue is concerned, I don't think it will be of any problem in your case. In case you are changing it using some UI event, just mark the private field as volatile. The documentation of the volatile keyword can be found here:
http://msdn.microsoft.com/en-us/library/x13ttww7.aspx

Resources