So I have a listview with a hierarchical data template containing signal graph.
<HierarchicalDataTemplate
DataType="{x:Type ViewModels:BusViewModel}"
ItemsSource ="{Binding Path = bits}"
>
<Components:SignalGraph
x:Name="signal_graph"
/>
If I remove an item from itemslist, the signalgraph remains and is still hooked onto the redraw event, so I'm having redraw events for items that are not on screen.
My first instinct was to go and change VirtualizingStackPanel.VirtualizationMode="Standard"
so as to be certain that the container wasn't being reused, but that is not enough to stop the redraws.
However, I am merely using the virtualizing tile panel from here:
http://blogs.msdn.com/b/dancre/archive/2006/02/16/implementing-a-virtualizingpanel-part-4-the-goods.aspx
and I don't think it implements recycling. It looks to just be using generator's remove and generatenext methods rather than the recycle method. So I'm rather confused as to why the generated objects are not being disposed of correctly. When I look in the cleanupItems method of the panel
private void CleanUpItems(int minDesiredGenerated, int maxDesiredGenerated)
{
UIElementCollection children = this.InternalChildren;
IItemContainerGenerator generator = this.ItemContainerGenerator;
for (int i = children.Count - 1; i >= 0; i--)
{
GeneratorPosition childGeneratorPos = new GeneratorPosition(i, 0);
int itemIndex = generator.IndexFromGeneratorPosition(childGeneratorPos);
if (itemIndex < minDesiredGenerated || itemIndex > maxDesiredGenerated)
{
generator.Remove(childGeneratorPos, 1);
RemoveInternalChildRange(i, 1);
}
}
}
I find that the panel's internal children does get reduced to 1, so I think that WPF is supposed to be taking care of most of this for me. I therefore am of the opinion that I probably need to implement IDisposable or something along these lines to ensure that the control is destroyed and all event handlers are detached.
How do I properly dispose of items when removing it from the observable collection that belongs to the listview's itemssource?
You are on the right track. Calling generator.Remove does remove the container from generators cache but if you have events or handlers remaining between the item and container the container will not get collected by GC.
Therefore release all the handlers and wpf will take care of removing container from memory, actually GC will remove it. Like you mentioned in first sentence you seem to have some drawing event. If you do not release that event, the container will not get finalized.
So simply release all your custom logic you have in there and you should do fine.
I just hooked onto the unloaded event and deattached the event handler, and now the code works fine. I am guessing behind the scenes the itemscontrol tries to destroy the object if there are no references to it or something
Related
I'm going to use pseudocode in this question, so please, refer to this in theoretical aspect (I mean, there will be some simplifications)
Assume this situation:
I have MainWindow of my application, which has TabControl. In order to remove item from its collection, I'm using static function of MainWindow as follows:
public static void CloseTab(string someKindOfTabIdentity)
{
var tab = myTabControl.Items.FirstOfDefault(someScenario);
if (tab != null)
{
myTabControl.Items.Remove(tab);
tab.Content = null;
tab = null;
GC.Collect();
}
}
Now, I have Page1, where I allow user to close tab, via some function, let's say
private void GoToPage2()
{
MainWindow.CreatePage2AddToTabControlAndNavigateToIt()
MainWindow.CloseTab(myCurrentPage1Tab);
App.Cursor = Cursors.Arrow;
}
This function should create new Tab, assign content to it and then, using MainWindow.CloseTab(myCurrentPage1Tab);, close current Tab, which contains Page1.
Here are questions:
What happens to memory alocation of Page1 after MainWindow.CloseTab(myCurrentPage1Tab); line?
What happens to memory alocation of Page1 if there is a code after MainWindow.CloseTab(myCurrentPage1Tab); line?
When Page1 is going to be fully released from memory?
Is there any better (more efficient) way to achieve this?
This simplified scenario is what is happening to my WPF application all the time and I'm concerned if this is a safe way to manage tab items and memory of my app.
The instance of Page1 will become eligible for garbage collection provided that it's not referenced by any other object that is still alive.
Nothing additional, unless the "code after MainWindow.CloseTab(myCurrentPage1Tab); line" does something with the Page1 reference that prevents the instance from being collected.
When the garbage collector has collected it. When this happens is nondeterministic, i.e. you don't really know when it happens and you shouldn't really care.
Well, there is no reason to explicitly call GC.Collect. This is almost always a bad idea. Provided that the instance of the Page1 is no longer referenced in your application, it will be collected eventually anyway. Also, I don't know why you are using static methods but I guess that's another story.
To sum up, you should simply make sure that there are no references in your code that keeps the page alive longer than than necessary but stay out of way of the garbage collector.
1-3
The garbage collector will remove unreferenced controls from memory when it gets round to doing so. Exactly when that happens will depend on what else your app is doing.
Exactly when some control is cleaned up by the collector doesn't usually matter. If it does to you then maybe you have a problem in your design.
4
Depends what you're doing. Almost all dev teams use mvvm with wpf. The usual approach would be to bind a collection of viewmodels to the itemssource of that tabcontrol and template those out into tabs. Removing a tab would involve removing a viewmodel out that list.
Only the current tab would be templated out into UI with this approach.
Your description makes this sound rather like navigation though. A common pattern used for mvvm style navigation is viewmodel first ( you should be able to google a bunch of examples ). Essentially this would involve exposing a property from a window viewmodel that would hold a viewmodel for current view. That would be bound to the content of a contentcontrol and templated into UI based on datatype.
I create controls dynamically (checkBoxes, listBox, trackBar...) on a Form, and i put menuStrip using the editor. I'd like to remove all the controls, and i'd like to keep the menu.
When i use this line:
this.Controls.Clear();
it removes the menu as well as the controls, however as i understood, menu items are not controls, they are on the form directly(i can see them if i write "this" and press a dot).
i tried to cycle over the controls and removed only if the type was one of the controls, but some control stayed while some are removed. I cycled using controls.Count. I tried to put the whole cycle to another while() and exit if Controls.Count > 1 like this:
while( this.Controls.Count > 1 )
{
for (int i = 0; i < this.Controls.Count; i++ )
{
if ((this.Controls[i].GetType() != typeof(MenuStrip)) )
{
this.Controls.RemoveAt(i);
}
}
}
It removes the controls and leave the menu alone, but the items that are disappears doesn't disapper in the same time, but some time later, i guess it's becouse the while runs more than one time.
My questions:
1. Why can't it remove all the control at once, while i iterate over the whole thing using controls.count as the upper bound.
2. What's the point of menuStrip as control while toolStripMenuItmes are not control.
This is the classic mistake of modifying the collection that you are iterating. Most collection classes generate an exception when you do that, unfortunately ControlCollection doesn't. It just misbehaves, you'll skip the next control after the one you remove. There's another nasty bug, the Controls.RemoveAt() method doesn't dispose the control you remove, it leaks forever.
Avoid the iteration bug by iterating backwards. And properly dispose, like this:
for (int i = this.Controls.Count-1; i >= 0; i--) {
if (this.Controls[i].GetType() != typeof(MenuStrip)) {
this.Controls[i].Dispose();
}
}
We have a TreeView in our application with the following requirements:
When an item is added:
The newly-added item is scrolled into view
The parent of the newly added item is also scrolled into view.
If they are too far away to both be seen at the same time, the item takes precedence.
This seems easy, simply scroll the parent into view first, then scroll the child.
The problem is when you call it like this:
parent.BringIntoView();
child.BringIntoView();
...only the second one seems to have any effect. The first one is basically ignored.
I then tried wrapping the second call in a BeginInvoke() call like this:
parent.BringIntoView();
Dispatcher.BeginInvoke((Action)(() => {
child.BringIntoView();
}));
Which does work, but now you can visibly see the TreeView scroll twice; once for the parent, then a moment later, for the child, which just looks bad.
So how can I call BringIntoView back-to-back but without the double-refresh issue of using the dispatcher?
Try using the Loaded event instead of the dispatcher. According to this article, it's a perfect fit for situations like this:
... we initially implemented the Loaded event so that
it would fire just after the window was rendered, but before any input
was processed. We figured that if it was ready enough for input, it
was ready enough for load-time initialization. But then we started to
trigger animations off of the Loaded event, and saw the problem; for a
split second you’d see the content render without the animation, then
you’d see the animation start. You might not always notice it, but it
was especially noticeable when you run the app remotely.
So we moved
Loaded so that it now fires after layout and data binding have had a
chance to run, but just before the first render. (And note that if
you do anything in your Loaded event handler that invalidates layout,
it might be necessary to re-run it before rendering.)
In other words, on Loaded you have the most up to date information about the physical layout of the element, but it hasn't actually rendered yet, so you should be safe from any "screen flicker" issues.
EDIT: To answer your question in the comments, you can wire up events "local" to the current method using a closure, like this:
EventHandler handler = null;
handler = (sender, e) => {
this.LayoutUpdated -= handler; // only run once
child.BringIntoView();
};
this.LayoutUpdated += handler;
By defining the handler inside the method, you are able to access the method's local variables (child) from within. Very similar to the Dispatcher call.
I'm not sure if relying on LayoutUpdated is a good idea, actually. It happens quite often so it may end up firing sooner than you need. It happens twice for individual Width and Height settings, for example. Another one to look into is ScrollViewer.ScrollChanged. Or you could avoid BringIntoView altogether and try manually examining the element sizes to calculate where to scroll to.
I have a tree of Visuals. The main window has a full-sized Canvas. The Canvas holds the root of some custom Visuals. The first custom Visual (A) can expand and contract based on what the user is viewing, which are in children (Bs, that are DrawingVisuals). As the main window resizes, I want to add children to or remove children from A. The same may happen when the user scrolls any direction in the view. When children (Bs) are added, I want them to be rendered. (The Bs that are removed are no problem because they are no longer visible anyway.)
Take a scenario: The main window is 640x480. I resize to twice that size, which would reveal more Bs, which then are added to A. So far I cannot get the newly added Bs to render, not even when using Invalidate???() methods of the FrameworkElement. I have tried Invalidate???() in the main window and in the A when Bs are added. (I have not with the Bs, figuring they are new.) It seems like Invalidate???() is not the way to go.
I have found a hack that allows the new Bs to render on resize. In the main window I can remove the A from the Canvas and add it back, which forces the tree to be rerendered:
this.contentCanvas.Children.RemoveRange(0, contentCanvas.Children.Count); // remove root
this.contentCanvas.Children.Add(this.CustomVisual_A); // readd root
For obvious reasons this is not desirable, especailly when scrolling. Specifically, the Bs that are being added and removed are managed in that way because they are quite large, and I need to reduce the memory footprint and increase rendering speed as much as possible. That's why I am going to the trouble.
Can someone help me understand what I need to do to get the newly added Bs to render? Thanks much!
I solved this. Even though the FrameworkElement is custom, you need to use the standard AddVisualChild() and RemoveVisualChild() methods to managed your children:
this.AddVisualChild(new B());
...
this.RemoveVisualChild(existingB);
You still maintain your own collection of Bs, but it seems that unless you use AddVisualChild() and RemoveVisualChild() the underlying FrameworkElement doesn't see the change (actually Panel, the super class of Canvas).
This may also involve the parent in notification in some way; not sure. Ultimately these collection management methods cause the OnVisualChildrenChanged() method, defined on Panel, to be invoked, so it should be implemented in the A. At that time I add/remove the child to/from my internal collection, and then invoke on to base:
protected override void OnVisualChildrenChanged(DependencyObject added, DependencyObject removed)
{
if (added != null)
{
this.children.Add((B) added);
}
if (removed != null)
{
this.children.Remove((B) removed);
}
base.OnVisualChildrenChanged(added, removed);
}
This causes some internal indication that rendering is necessary.
I figured something like that had to be occurring because removing A from my custom Canvas and re-adding the A forced the render.
There are a few calls for this...
element.UpdateLayout();
element.InvalidateArrange();
element.InvalidateMeasure();
element.InvalidateVisual();
How do I correctly dispose an user control in a FlowLayoutPanel ?
Does flowlayoutpanel1.Controls.RemoveAt(i) suffice?
I just can't find a .Dispose() for flowlayoutpanel1.Controls...
If you wish to remove all the controls, you can iterate through the control collection backwards, rather than creating a copy (see below).
I have found this provides the best solution, particularly if you intend to re-populate it afterwards. Forcing GC to collect helps keep memory use in check where there are a large number of controls.
FlowLayoutPanel.SuspendLayout();
if (FlowLayoutPanel.Controls.Count > 0) {
for (int i = (FlowLayoutPanel.Controls.Count - 1); i >= 0; i--) {
Control c = FlowLayoutPanel.Controls[i];
c.SomeEvent -= SomeEvent_Handler;
c.Dispose();
}
GC.Collect();
}
FlowLayoutPanel.ResumeLayout();
Do you want to dispose all the controls in the FlowLayoutPanel or all of them? If you want to dispose all of them, just dispose the FlowLayoutPanel. Disposing a control disposes everything in the Controls collection as well. If you want to dispose an individual control, call that control's Dispose method; the FlowLayoutPanel will automatically remove it from its Controls collection.
If you want to dispose all the controls, but not the FlowLayoutPanel itself, it's a bit trickier. You can't just foreach over the Controls collection and dispose each control because that would cause the Controls collection to be modified. Instead, you could copy the Controls collection to a separate list and dispose of them from there.
If the control has Dispose() method just call it after removing it from the panel.