In my project I have to use WPF to place a big set of similar user controls(around 2000) on Canvas object. Basically, it's just a set of rectangles, that can change visibility, can be selected and store data object inside.
I add new controls with help of attached property like this:
public static readonly DependencyProperty VisualStaticBlocksProperty =
DependencyProperty.RegisterAttached("VisualStaticBlocks", typeof(ObservableCollection<VisualBlockViewModel>), typeof(BindableBlocksBehaviour),
new UIPropertyMetadata(null, VisualStaticBlocksPropertyChanged));
private static void VisualStaticBlocksPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
ClickSightView clickSight = source as ClickSightView;
ObservableCollection<VisualBlockViewModel> visualBlocks = e.NewValue as ObservableCollection<VisualBlockViewModel>;
if (clickSight != null && visualBlocks != null)
{
foreach (VisualBlockViewModel visualBlock in visualBlocks)
{
clickSight.StaticBlocksCanvas.Children.Add(new VisualBlockView(visualBlock));
}
}
}
However, it takes a lot of time to build all of them(around 2 seconds). I used a profiler to check that main problem is in LoadBaml() method, which is called in InitializeComponent() method.
As I understand, LoadBaml() is used to parse xaml markup. Is it possible somehow to cache the LoadBaml() result for component and reuse it instead of parse xaml each time I create new control instance?
EDIT:
To represent this set of objects visually I have created user control with Canvas on it, and created attached property VisualStaticBlocks to attachblock view models(type VisualBlockViewModel) to this control and insert visual block instances(type VisualBlockView) directly to Canvas.
EDIT2:
I've solved the problem by giving up using user controls for this purpose at all.
As my controls are quite simple, I used Rectangle() class instead with 3 manually added bindings and 3 manually added events. Of course, there were no InitializeComponent() calls at all.It allowed me to build the set of 2000 rectangles in 200 miliseconds, which is 10 times faster.
Anyway, still will be grateful for information if I can clone similar objects without loading BAML each time.
It sounds like you have an issue with the time it takes to create visual elements. I can see why you think you need to call InitializeComponent, but that is not how WPF works.
As noted here:
The call to InitializeComponent() (which is usually called in the default constructor of at least Window and UserControl) is actually a method call to the partial class of the control (rather than a call up the object hierarchy as I first expected).
Which leads me to suspect you do not understand how (or why) InitializeComponent works; it is impossible to call it once to build multiple elements and externally, no less.
You are using ObservableCollection, which neither works well with large data sets nor complex views. Consider using a thread-safe ObservableCollection and add the data objects on a background thread. This shouldn't be an issue because you're adding data objects (view models) versus visual objects (views); visual objects should be added on the same thread (UI) they are created.
It would help to provide additional information as you have not explained how you represent these objects visually. Is the collection bound to an ItemsControl and does it define a DataTemplate to visually represent each data object?
Related
Firstly, I am very proficient at WPF and rarely get thrown by things, and rarely ever have to post a question. To be fair, I have only ever used one main Window with all embedded Content so it is really flat so this is the first time I have had to use additional floating Windows.
I have a Window that I raise after assigning it a View Model to its Content properly which duly shows and renders as expected using the data template etc. I hook into the Window.Close event after creation and remove the Close even method when closed. Pseudocode as follows:
Creating the Window:
private void CreateWin()
{
// Just in case a previous instance is open
CloseWin();
_myWin = new Window()
{
Content = new MyViewModel()
};
_myWin.Closed += OnMyWinClosed;
_myWin.Show();
}
When the Window is closed:
private void OnMyWinClosed(object sender, EventArgs e)
{
CloseWin();
}
Cleanind up:
private void CloseWin()
{
if (_myWin == null)
return;
_myWin.Content = null;
_myWin.Closed -= OnMyWinClosed;
_myWin = null;
}
I run the code to create the Window and set the content to the view model and when the I close the Window it soon gets destroyed and all is OK.
Now the issue:
The view model that I am assigning is not necessarily complex but it does a few things, even then, it is probably not relevant but, if I assign an existing view model that is still in memory rather than a new instance of the same view model type (as this code shows), I can create 50 instances of the Window but they do not get destroyed until the application terminates.
My actual window is a subclassed Window so that I can add a breakpoint to the ~ destructor to check the finalisation. I have also called the following just to make sure but no destruction, although a bit over the top:
GC.Collect()
GC.WaitForPendingFinalizers();
GC.Collect()
You will also notice, for extra measure, that I set the Window.Content = null afterward just to really detach it from the Window.
Nothing in the associated view, model, or data template has any events hooking the view model to the UI other than the inbuilt PropertyChanged. However, that should also make no difference, and also the same view model independently set has no issue.
My application went from about 110MB to 420MB when opening all so I could also see the memory creep up. I have a good laptop so is it that it would eventually clear but takes longer for larger objects? If that is the case then that does not make sense.
In summary, it almost seems that ViewModel’s assigned to a Window.Content property will prevent the Window from being garbage collected if the ViewModel is from another source and not created on the fly for the specific Window.
Surely that cannot be right?
Other points:
I have tried setting the Owner and not setting the Owner which makes no difference.
I have created these windows in a loop of 100 and destroying them to see if has anything to do with bigger objects taking a long time, but no change.
I'm trying to create a function that adds usercontrols to my mainform. I want this function to be available from the usercontrols themselves as well, so I'm making it static.
My problem is that when it's static I can't use this.Controls.Add(ucontrol);, I tried to change it to ActiveForm.Controls.Add(ucontrol); but then ActiveForm is NULL and has no Controls property (throws an exception).
UPDATE #1: I ended up doing something like this inside the usercontrol:
MainForm ref_to_mainform_to_gain_access_to_functions = (MainForm)Parent;
ref_to_mainform_to_gain_access_to_functions.DisplayControl(ucontrol);
maybe it's even better, but I still assume it's possible to have access to the mainform from a static method?
UPDATE #2:
Application.OpenForms["FormName"]
worked from static method.
I still try to find out if that usercontrol entire purpose is serve as a menu with buttons that shows other usercontrols onclick, is it still a 'design-crime' to call a function on mainform (a function that shows usercontrols) from a user control? (like I did in 'update #1')
By adding controls from a user control to its parent you are breaking the separation of concerns principle and you are tightly coupling both controls/forms. As suggested by Hans Passant you should use events. Please refer to:
best practices to call methods in the parent form and access gui elements in parent form in c#
Some months ago I started developing a Silverlight application on my own. I quickly discovered that I was unable to get expected garbage collection for most of my controls. I struggled for about a week with WinDBG and ANTS memory profiler and then found the "DataTemplate memory leak" strand on the Silverlight forum (http://forums.silverlight.net/forums/t/171739.aspx).
Given that so many people seemed to be frustrated with various memory issues I decided to delay further investigation into the memory situation until the most obvious issue was resolved.
Anyway, now I'm looking into the issue again and I realise that the problem I'm having is much more fundamental than I had first thought. I simply don't have a paradigm for writing garbage collectable Silverlight controls when: a) the control has dependency properties that can be bound to, and b) the control can be unloaded from one control and then subsequently loaded again.
I'm starting to think that the second of these demands is too great. Can anyone confirm this?
To give a tiny bit more detail, the most robust pattern I can come up with for writing good, garbage collectable Silverlight controls is as follows:
1) When a Control's Template is applied (in the OnApplyTemplate override) I setup any internal bindings between local properties and TemplateParts. For example, I might setup a Binding between a local property called CanSearch and a button.
if (x_Button_Search != null)
{
Binding b = new Binding("CanSearch");
b.Source = this;
this.x_Button_Search.SetBinding(Button.IsEnabledProperty, b);
}
2) When the Control raises the Unloaded event, I clear the internal bindings and un-wire any eventhandlers.
if (x_Button_Search != null)
{
this.x_Button_Search.ClearValue(Button.IsEnabledProperty);
}
This seems to be the cleanest way of ensuring that no trailing references exist between the x_Button_Search element and the Control. I don't know if this is strictly necessary.
3) Again, when the Control raises the Unloaded event, I clear bindings to existing dependency properties.
this.ClearValue(SearchParametersProperty);
If I fail to do this I can cause leaks. For example, if the SearchParameters property was bound to some INotifyPropertyChanged object then a reference to the Control remains in the PropertyChanged event on the INotifyPropertyChanged object to which I am bound even after the control has been unloaded i.e. the View will stay around as long as the Model and that may not be desired.
4) I 'flicker' the Template value so that next time the control is loaded, the template is reapplied and the OnApplyTemplate method is fired again.
var oldTemplate = this.Template;
this.Template = null;
this.Template = oldTemplate;
The reason to do 4 is that I need to reinstate bindings when the Control is reloaded onto a page. In Silverlight, there are two points of entry through which to do this: in the OnApplyTemplate override or after the control fires the Loaded event. As I want to enforce binding values before the control has been loaded (to avoid flickering), there is only one available entry point available, OnApplyTemplate. I have to flicker the template in order to force the template to reapply when the control is reloaded.
It appears this pattern up to point 3 is the bare minimum for providing garbage collected controls.
My problem comes when you want to unload your control (remove it from a Panel for example) and subsequently reload it. Any dependency properties on the control have been set to null in point 3. For example, imagine there is a binding on the declaration of the control e.g. . As far as I can tell, there is no way of reinstating this Binding once the value of SearchParameters has been set to null, it's not part of a Template after all. The result is that when the control is re-loaded it's as if the value of SearchParameters was null. So I either skip out step 3 in the pattern and get a reloadable control that is not garbage collected, or I keep 3 and get an unreloadable control.
What you do in 1) seems really strange. Why initiating a binding to the template in code and not in xaml?
We have solved lots of memory leak issues in silverlight using this software
http://memprofiler.com/
EDIT
For more control over the binding, you can use
{Binding Property, RelativeSource={RelativeSource TemplatedParent}}
That way the implicit converters are used as expected and you can also specify your own. And I believe that BindingMode TwoWay works as well.
Good luck!
I want to open a WPF4/EF4 form in AddNew mode so the user can start entering data in bound controls before any data has been selected from the database. I already have an "Add New Record" button but it only works with a populated DataContext (my CollectionViewSource). Here is the code so far:
private void btnAddNewRecord_Click(object sender, RoutedEventArgs e)
{
LabSample newEntity = _labEntitiesContext.LabSamples.CreateObject<LabSample>();
_labEntitiesContext.LabSamples.AddObject(newEntity);
_labSamplesListCollectionView.AddNewItem(newEntity);
}
Background: This is a basic WPF app with bound controls. I started with an Entity Framework model that appears in my DataSources window. I dragged my LabSample entity from the DataSources window and let it create my CollectionViewSource (labSamplesViewSource) in the XAML's Windows.Resources section. The DataContext for all my controls is the labSamplesViewSource. I create a new LabEntities object called _labEntitiesContext as the window is instantiated. I use _labEntitiesContext to build my filtered ObjectQuery(of LabSample) and to SaveChanges, but I'm a little confused as to how this _labEntitiesContext is hooked up to my CollectionViewSource. If you could clarify this along with answering my question that would be helpful. Note: I'm not ready to use MVVM.
When the window loads I use this.FindResource to grab a reference to the CollectionViewSource in a class level variable named _labSamplesCollectionViewSource. I allow the user to enter search fields to populate the screen with data. My LoadData routine looks something like this:
System.Data.Objects.ObjectQuery<LabSample> labSamplesObjectQuery = this.GetLabSamplesFiltered_Query(_labEntitiesContext, sampleID_LIKE, xxx_LIKE, yyy_LIKE);
System.Data.Objects.ObjectResult<LabSample> labSamplesObjectResult = labSamplesObjectQuery.Execute(System.Data.Objects.MergeOption.AppendOnly);
_labSamplesCollectionViewSource.Source = new System.Collections.ObjectModel.ObservableCollection<LabSample>(labSamplesObjectResult);
_labSamplesListCollectionView = (ListCollectionView)_labSamplesCollectionViewSource.View;
The _labSamplesListCollectionView class level variable set above is used in my btnAddNewRecord_Click code. Before LoadData is called the _labSamplesListCollectionView is null causing my AddNew code to fail with "Object reference not set to an instance of an object".
How can I make this work? I'm wondering if I should be making use of _labSamplesListCollectionView.AddNew instead of my current technique but I couldn't get that work either. Your help will be greatly appreciated.
I am writing an app that does something similar. I am however using MVVM pattern, which allows me to do some neat things. In mine, I am working with Shipments. On the ShipmentsView, I can click an "Add New" button which fires off a bound command property which is housed in the associated ViewModel class. That command methods looks like the following: Note: Views in this context are not CollectionView but refer to MVVM View classes.
Dim NewShipment = New Shipment()
_Context.AddToShipments(NewShipment)
Dim ShipVM = New ShipmentViewModel(NewShipment)
ShipmentVMCollection.Add(ShipVM)
Dim NewShipmentView as ShipmentView(ShipVM)
My ShipmentView handles it's placement and visiblility, and my Shipment object has it's property values initialized so that it does not immediately present errors via it's validation handlers. This way the user can create a new shipment and if they get sidetracked they can save it and come back to it without having a bunch of mandatory fields.
When I use a CollectionViewSource, I populate it with an ObservableCollection of my entities, and then add the entities to that observable collection when I create them. ObservableCollection implements INotifyPropertyChanged and INotifyCollectionChanged events and notifies the UI when something happens, and it all works through the CollectionViewSource.
You might take a look at the MVVM pattern which is really good for moving data and keeping it in the proper scope, and there are some good MVVM frameworks out there that will help you make a nice application with MVVM.
MVVM may be a bit of overkill for your app if it is small. But if it gets over more than just a few Views it is going to get unwieldy and hard to move data back and forth and keep it current, and maintainable.
Wiki Article for MVVM - a pretty good place to start and get links
This is my Constructor for one of my ViewModels. I realize you don't want to implement in MVVM right now, but a code behind would be similar. In this instance, I am using a background worker to get my entity records, (the constructor call be for that and the View setting immediately afterward can be disregarded), then I link up my CVS, Populate it with my ObservableCollection, and set it's View to a field so I can filter on it later.
Public Sub New(ByRef MyView As NTMsView)
Me.New(ViewManagerService.CreateInstance, ViewModelUIService.CreateInstance)
NTMsBackgroundWorker.RunWorkerAsync()
_View = MyView
_NTMCollectionViewSource = _View.FindResource("NTMCollectionViewSource")
_NTMCollectionViewSource.Source = NTMs
_NTMCollectionView = _NTMCollectionViewSource.View
End Sub
This is an example of my AddRecord method. Then I instance a new object, add it to the appropriate collection in the Context, Save it, execute a stored procedure, then refresh the context since the stored procedure did a few things to the record. Then I add the object to my Observable.
Private Sub AddNTM()
'Create an NTM Object.
Dim NewNTM As New NTMShipment()
'Add it to the context
_context.AddToNTMShipments(NewNTM)
_context.SaveChanges()
_context.MakeNewSecurityID(NewNTM.NTMShipID)
_context.Refresh(RefreshMode.StoreWins, NewNTM)
'Wrap it in a ViewModel and Add it to the NTMs collection
NTMs.Add(New NTMViewModel(NewNTM))
End Sub
As for creating a new entity before your CollectionViewSource is created, a couple of questions. Is your edit forms datacontext related to the CVS? In my forms, the CVS is only used in conjunction with ItemsControls since it is displaying a collection of items. If your edit forms controls are dissociated from the CVS, you should not have much trouble populating them with a new entity and when it comes time to save, check to see if CVS is null and if so, create it then populate it.
If that is not a good answer, could you expand on how your application is structured?
Instead of opening the Window in AddNew mode I disable all data entry controls when the window loads or the when a search returns no records. When the "Add New Record" button is clicked I ALWAYS start over with a new data context that contains just one new entity. This means I have to prompt to save changes if any dirty (modified) records exist. The prompt allows the user to save changes, discard changes or continue editing (never entering AddNew mode). Here is the AddNew code:
MessageBoxResult response = PromptToSaveChanges(ReasonForPromptToSave.LoadingData);
if (response == MessageBoxResult.Cancel) return;
LabSample newEntity = _labEntitiesContext.LabSamples.CreateObject<LabSample>();
_labEntitiesContext.LabSamples.AddObject(newEntity);
_labSamplesCollectionViewSource.Source = new ObservableCollection<LabSample>();
_labSamplesListCollectionView = (ListCollectionView)_labSamplesCollectionViewSource.View;
_labSamplesListCollectionView.AddNewItem(newEntity);
_labSamplesListCollectionView.CommitNew();
_labSamplesListCollectionView.Refresh();
Here are my steps to put the window in AddNew mode:
1) Prompt to save changes.
2) Create a new entity and add it to my data context.
3) Create a new ObservableCollection of my entity type and assign it to the .Source of my CollectionViewSource. Note the _labSamplesCollectionViewSource is a reference to the XAML's CollectionViewSource that was auto-generated by dragging a table from the data sources window.
4) Assign the .View of the CollectionViewSource to a class level ListCollectionView variable.
5) Add the new entity to the ListCollectionView that was just created.
6) Call CommitNew and Refresh on the ListCollectionView
I have answered my own question here, but keep in mind that the answer is the result of trial and error and may not be ideal. Regarding my confusion as to how the _labEntitiesContext is hooked up to the CollectionViewSource I believe the answer is in the line that reads _labSamplesListCollectionView.AddNewItem(newEntity), but I'd like to see an explanation of how all of the objects reference each other.
My final comment/question is that I'm disappointed at how hard it is to find a standard reference application or document that teaches non-MVVM WPF/Entity Framework databinding in detail. Microsoft promotes drag-and-drop binding but leaves us without a reference on how to build a complete application. I'll move on to MVVM soon, meanwhile if anyone can direct me to a GREAT resource or feature complete application that is WPF, non-MVVM and Entity Framework I would greatly appreciate it.
I'm trying to implement the following: I have an Items Manager, that has an Item class inside. Item class can store two possible visual representations of it - BitmapImage(bitmap) and UserControl(vector).
Then later, in the game, I need to share the same image or vector control between all possible places it takes place. For example, consider 10 trees on the map, and all point to the same vector control. Or in some cases this can be bitmap image source.
So, the problem is that BitmapImage source can be easily shared in the application by multiple UIElements. However, when I try to share vector control, it fails, and says Child Element is already a Child element of another control. I want to know how to organize this in the best way. For example replace UserControl with other type of control, or storage, however I need to be sure it supports Storyboard animations inside.
The code looks like this:
if (bi.item.BitmapSource != null)
{
Image previewImage = new Image();
previewImage.Source = bi.item.BitmapSource;
itemPane.ItemPreviewCanvas.Children.Add(previewImage);
} else
if (bi.item.VectorSource != null)
{
UserControl previewControl = bi.item.VectorSource;
itemPane.ItemPreviewCanvas.Children.Add(previewControl);
}
Or it is not possible to share same control in different places, then what is the best way to make a copy, or the best way to store vector data.
Thanks in advance
So, I found the problem. It is possible to attach the same UserControl to different controls.
However, when on update I was deleting control, and then filling up it again with a new pointer, that sometimes was the same as before deleting, somehow it was still in memory. And so it was like 2 same user control attached to the same parent.
I added a line of code that was cleaning all children in control, before updating it with new vector UserControl, and now works like a charm.