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.
Related
I'm using the latest version of Caliburn Micro (4.0.173) in an application leveraging IoC.
I do not have a sample project yet has my sandbox is using nugets from Telerik that are not easily findable. But I think a detailed explanation can get the point across:
I have a ProfileEditorViewModel : Conductor<IScreen>.Collection.OneActive resolved by the container (singleton) through the constructor of my ShellViewModel and I use the I use the WindowsManager to open my ProfileEditorViewModel in a different window:
public class ShellViewModel : IShellViewModel{
private readonly IProfileEditorViewModel _profileEditor;
private readonly IWindowManager _windowManager;
public ShellViewModel(IWindowManager windowManager, IProfileEditorViewModel profileEditor){
_windowManager = windowManager;
_profileEditor = profileEditor;
}
//Method triggered by an Action from the View
public void OpenProfileEditor(){
_windowManager.ShowWindowAsync(_profileEditor, null, null);
}
}
When I close the ProfileEditor window (clicking on the top right red cross), it triggers a closing type deactivation, which is coherent with the implementation of a conductor (not being conducted itself) being shutdown. The Screens collection is being wiped and some more cleaning is being done through the Screen.cs implementation of Caliburn.
However, for this application, I'm facing "the view not being unbound" issue (https://github.com/Caliburn-Micro/Caliburn.Micro/issues/361) and next time I open the ProfileEditor, it'll bind a new view (because the Views Collection of my viewmodel was empty) and it create some issue related to UI components used in the view (basically one event from the viewmodel triggers similar actions, on the view(s) side, that all come back to the viewmodel, which create some identification issue)
Reading through the issue 361, I'm currently able to catch the Unloaded event of my ProfileEditor (once closed), and basically clean the DataContext of any view associated to the viewmodel (before Screen cleans the Screens collections). Next time I open the ProfileEditor, it'll bind a new view and it'll be the only one = it works.
However clearing the DataContext may produce some other issue down the road.
What I would like to do, is to avoid clearing the View collection of the ProfileEditorViewModel upon closing. So Caliburn can use this reference the next time it needs to resolve a window/view for the ProfileEditorViewModel (instead of looking for a new one).
Is it possible to intercept the Deactivation / Closing strategy and change the close parameter to false ?
Another solution may be for my ShellView to be a Conductor<>.Collection.AllActive but I can't wrap my head around teh management of a window closing. How to intercept the ProfileEditor windows closing and proceeding with a deactivation of the ProfileEditorViewModel instead of a closing.
I hope it make sense :)
Thank you in advance for your help
SOLVED
My problem is a little weird and i'm not even sure why opening a window should have any effect at all on my list binding, so little hard to explain, so here is a video showing what happens:
https://www.youtube.com/watch?v=bZ03l8OHEY4
I have 2 Pages, Start Page and Main Page. In Start Page user can select between (supported)devices detected on COM ports. When a device is clicked, the program reads the device and creates a view model list (asynchronously with BackgroundWorker) based on the data on the device, here i also instantiate and show a loading window/dialog. When clicking the device again the program then goes to Main Page and displays the view model list as a view list (with ItemsControl in xaml) that was created for this device.
So far everything works fine.
However, if i have 2 devices a problem occurs if i do A and B but not when i do C. And A and B both works if i do not instatiate the loading window/dialog, i demonstrate this last in the youtube video(it also breaks if i instatiate but do not .Show()).
A) The sameway i said as above, then i go back to Start Page then reads and opens the other device. (this is the second thing i show in the youtube video)
B) The sameway i said as above, then i read and open the other device without going back to start page.
C) Instead, read both devices before opening any of them (stay on Start Page), then open the first device, then open the second device. (This is the first thing i show in the youtube video)
Top image is showing that the displayed list is the same as the list that is actually in the memory of the computer. Bottom image shows what happens when doing like described in B. As you can see the list on screen is different from what the LiveVisualTree says the list actually is.
When doing A/B the list that is displayed is not updated to the created view model list of the other device (the list of the first device is still shown). The view model list is created correctly (yes, i have double and triple checked) the view list is just not updated. Even if i manually use OnPropertyChanged() for the list.
However, when i do C everything works flawlessly. The view list is updated to display what the view model list contains.
Some code i think is relevant:
user clicks on device:
public static void DeviceClick(ComPortVM comPort)
{
CurrentComPort = comPort;
if (comPort.Device == null)
{
ShowReadingDeviceDialog(true);
BackgroundWorker workThread = new BackgroundWorker();
workThread.DoWork += new DoWorkEventHandler(BackgroundParameterReader_DoWork);
workThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BackgroundNewInverter_RunWorkerCompleted);
workThread.RunWorkerAsync(new ReadParameterArgs(basic, false));
}
else
{
OpenDevice();
}
}
OBS: when commenting out ShowReadingDeviceDialog(true); the problem does not occur and everything works
ShowReadingDeviceDialog(true);
private static void ShowReadingDeviceDialog(bool show)
{
if (ReadingDeviceDialog != null)
{
ReadingDeviceDialog.Close();
}
if (show)
{
MainWindow.instance.IsEnabled = false;
MainWindow.SetCursor(System.Windows.Input.Cursors.Wait);
ReadingDeviceDialog = new OpeningInverterDialog();
ReadingDeviceDialog.Owner = MainWindow.instance;
ReadingDeviceDialog.Show();
}
else
{
MainWindow.SetCursor(System.Windows.Input.Cursors.Arrow);
MainWindow.instance.IsEnabled = true;
}
}
OBS: when not instantiating a new ReadingDeviceDialog the problem does not occur and everything works
OpenDevice
private static void OpenDevice()
{
if (!isOpening)
{
isOpening = true;
if (!InfoPanels.ContainsKey(CurrentDevice))
{
InfoPanels.Add(CurrentDevice, new InfoPanelVM());
InfoPanels[CurrentDevice].SetUp(CurrentDevice);
}
MainViewModel.instance.RefreshInfoPanel();
startDelay.Interval = TimeSpan.FromMilliseconds(100);
startDelay.Start();
}
}
startDelay Tick
private static void StartDelay_Tick(object sender, EventArgs e)
{
MainWindow.OpenPage("MainPage");
if (!paramTabBars.ContainsKey(CurrentDevice))
{
MainPage.ClearFrame();
paramTabBars.Add(CurrentDevice, new ParamTabBarVM());
ParamTabBar.SetUp(CurrentDevice);
}
else
{
ParamTabBar.OpenTab("");
}
startDelay.Stop();
isOpening = false;
ShowReadDeviceDialog(false);
MainViewModel.instance.RefreshParamTabBtns();
}
It's the ParamBarTab that contains the list that causes problems, called ParamTabBtns. Using MainViewModel.instance.RefreshParamTabBtns(); simply uses OnPropertyChanged(ParamTabBtns) for the currently selected Device, this fixed a similiar problem i had with the view list not updating to fit view model list. However this fix does not work now when i instantiate a loading dialog window.
Since the binding had worked all the time before i started instantiating a loading dialog i do not think the problem is in XAML, so i won't bother posting that to.
I know this question maybe weridly asked, but since i have no clue on what going on i don't really know what else information i should provide. I'm mostly just hoping for someone giving me ideas as what kind of thing that might possibly be the thing i should try to find, but right now i'm totally blind.
Edit: I'm starting to suspect it's a bug in WPF and not on my side? LiveVisualTree the DataContext gives me the right list, but that list is not shown on screen. Screen cap with green arrows is when everything works, with red arrows is when i did like in A. The list to the side that says 15 in length is the list that is supposed to be shown in the application, yet it isnt.
Isn't LiveVisualTree supposed to show me the binding? If the binding works in LiveVisualTree isn't it supposed to work in the application too? Isn't that what LiveVisualTree is used for? Debugging? So shouldnt the bindings in LiveVisualTree reflect the actual bindings happening inside the application?
Edit 2: I tried BindingOperations.EnableCollectionSynchronization following http://10rem.net/blog/2012/01/20/wpf-45-cross-thread-collection-synchronization-redux
However, this had no impact on anything. So it probably isn't threading related either? And it doesnt make sense that it is threading related anyway, since i can change the collection as i please when the dialog window has not been intantiated in scenario A and B. And again, the dialog window has noting the do with the collection (no references, no method calls, no nothing)
LiveVisualTree
EDIT 3, SOLUTION:
Apparently when using
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
WPF creates a new instance of the datacontext, in the constructor of my MainViewModel i had static reference to itself. But when i instantiated a new window that reference got pointed to the newly created datacontext instead, which destroyed the bindings.
To fix this i removed the code from above from all xaml files that had it and instead put this in the construction (code behind)
DataContext = MainViewModel.instance;
Apparently when using
<Window.DataContext>
<vm:MainViewModel/>
</Window.DataContext>
WPF creates a new instance of the datacontext, in the constructor of my MainViewModel i had static reference to itself. But when i instantiated a new window that reference got pointed to the newly created datacontext instead, which destroyed the bindings. To fix this i removed the code from above from all xaml files that had it and instead put this in the construction (code behind):
DataContext = MainViewModel.instance;
My question is what is the ideal way to initialize child window in WPF, MVVM way?
I have a WPF window , let's call it as ParentWindow having its view Model class - ParentWindowViewModel. On click of a button on the ParentWindow UI , I launch a new WPF window - ChildWindow like below
ChildWindow r = new ChildWindow ();
r.ShowDialog();
r.WindowStartupLocation = WindowStartupLocation.CenterScreen;
Now, the ChildWindow has its own viewModel like - ChildWindowViewModel. The Child Window has the datacontext set in its xaml as
<Window.DataContext>
<viewModel:ChildWindowViewModel/>
</Window.DataContext>
On Click of button in the ParentWindow, When i Launch the Child Window, I need to pass certain values to the Child Window, which will be used to initialize the Child Window. Without these values the child window cannot be initialized.Every time I click the button to Launch the child window, the values being passed to child window will differ based on some other selected items in the Parent Window.
I would do something like this (without error checking):
in ParentWindow.xaml.cs
private void Some_Event_In_Parent_Window()
{
ParentWindowViewModel pvm = DataContext as ParentWindowViewModel;
ChildWindow cw = new ChildWindow(pvm.Element1, pvm.Element2);
}
int ChildWindow.xaml.cs
public ChildWindow(bool elem1, string elem2)
{
InitializeComponents();
DataContext = new ChildWindowViewModel(elem1, elem2);
}
If you are dealing with minimal elements that need to be transferred between windows/VM's, and it is mainly about sending some form of "state" or "value", then there isnt too much issue with initializing the Viewmodel in the code behind. Remember that the <viewmodel:ChildWindowViewModel/> is equivalent to DataContext = new ChildWindowViewModel() in your code behind. While yes, you can create spaghetti code, or confusing dependencies by not adhering to patterns; you can also over engineer the crap of something that did not require the effort and can be just as confusing.
I find that there is an obsession with keeping your code behind empty (i have the same obsession). Remember, that the Code behind is there for a reason, and you CAN use it. Sometimes it isnt worth over-complicating your code base by implementing some big pattern if you have a single one off requirement that can be handled in code behind with some added comments.
Aside from Event Handlers, I utilize the Code Behind for these main use cases:
There is an adjustment needed to the UI that is too complicated to handle in the XAML alone. Example: I need a weird string concatenation and logic of some inputted text fields.
There is some minimal state or data needed to be transferred between Views. (Like your requirement)
There is some sort of logic that needs to happen that is UI specific and not related to the underlying data or ViewModel. (This is rare, and almost always a small one off).
In terms of "is this ideal" for MVVM; it depends on your definition of ideal.
Can this be handled by some other design pattern? Probably...? But is it worth it.
Does implementing said pattern add bloat or overhead that only solves a small problem? Maybe. That is for you to decide.
Are you going to be repeating this implementation more than once? If so you may have some bad design to rethink.
Does implementing this solution of using Code behind solve your issue in a speedy way, that is moderately maintainable and readable? If so, then I wouldnt see a problem.
Ideal is not always defined by rules. It is also specific to your needs and requirements.
It isnt always easy to determine where your use case should be handled. View, ViewModel, maybe a Service, or a Singleton state class.
If your use case is this one window, Code behind is fine (In my opinion). If you are doing this for 20 windows, you may want a Service to maintain the state somehow. Think of it more - if your code is SOLID and DRY
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?
I am having a bit of a problem with my UI hanging even though I am using a Dispatcher, and before I get any further I'm wondering if it's the way I'm handling the data retrieval.
Right now I have my main window creating a View and ViewModel. Then, inside of a new Thread (using a Dispatcher) it sets the View.DataContext = ViewModel. A very large ObservableCollection is lazily created when the binding kicks in which is causing the slowdown. However, it seems that some of the other UI items that should be showing up before that slowdow don't actually show up.
private void ButtonClick(Object sender, RoutedEventArgs e)
{
MyView view = new MyView();
MyViewModel vm = new MyViewModel();
TabItem tabItem = new TabItem();
tabItem.Header = "MyView";
tabItem.Content = view;
MyTabCollection.Items.Add(tabItem);
Window working = new Working();
working.Show();
ThreadStart thread = delegate()
{
DispatcherOperation operation = Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
new Action(delegate()
{
view.DataContext = vm;
((FrameworkElement)view.Parent).Focus();
working.Close();
}
)
);
};
Thread theThread = new Thread(thread);
theThread.Start();
}
This basically says it's supposed to create a view and a viewmodel, then add the view to the tab collection I have (which means it should show the new tab at the least). And, it should also show a "Working..." window. After that, a separate thread is supposed to link the ViewModel to the view, focus on that tab and close the working window. The problem is that the first portion doesn't show until everything is done; The tab is not displayed and the working window is not shown until after the new Thread actually finishes (which causes the Working window to show/close right away). I'm guessing it might have to do with the way I retrieve the data, but I'm not sure. Here is the way it does it:
Create View
Create ViewModel
Create TabItem with Content set to the View and add the TabItem to the TabCollection.
Create/Show the "Working..." window
Dispatcher: Set the View.DataContext = ViewModel. This event sets off the DataBindings, which in turn grab the ObservableCollection. Since the OC is created Lazily it is now being created (this is the bottleneck). <-- Is this messing up my separate thread/dispatcher?
Dispatcher: Set Focus to the tab
Close the "Working..." window
All your extra thread is doing is marshalling another call back to the dispatcher thread. Presumably you actually want to do work on the extra thread, or there's no point in creating it.
Ideally your extra thread should be fetching all the data appropriately, leaving you only to actually connect it all up in the dispatcher thread. The important thing is to decide which work you need to do on the UI thread and which work you need to do on the background thread.
Obviously your analysis of the problem is correct. Your view model is lazily loading data when it is needed, and this is not happening until the Dispatcher callback, at which point you are back on the UI thread again and everything is locked up.
In my opinion, the solution is to do the threading in the data access layer:
For collections: You can define special collections that return only items that have already been loaded from the upstream data source, then trigger loading of additional items on a separate thread when someone subscribes to INotifyCollectionChanged. When the additional items arreive, fire INotifyCollectionChanged events. When INotifyCollectionChanged is unsubscribed, cancel any pending load.
For totals and the like: Same idea. As data comes in the total increases and events occur (automatically for DependencyProperty or using INotifyPropertyChanged).
In addition, the data layer should have a parallel property to each collection, sum, or other delay-loaded value indicating whether it is fully loaded or not, allowing the UI to gray out sections that aren't fully loaded. It is also convenient to have an overall "loading" flag somewhere that can be used to gray out UI sections when anything at all is loading (easier to write the UI this way).
Note that sometimes an operation must block until the actual data has been retrieved. I think the easiest thing in this case is to provide methods in the data layer to force data to be loaded synchronously.
Your DispatcherPriority is set to Normal - try setting it to Background as this may improve the rendering