WPF disposing of view and viewmodel correctly - c#

I've written an Outlook add-in (OL2010). It has a menu on the ribbon bar with various icons that open new windows that do stuff (hope that's not too in-depth ;)). An example of one of the icon Click handlers is below.
public void OnViewMyTracksClick(Office.IRibbonControl control)
{
try {
MyTracksViewModel viewModel = new MyTracksViewModel();
MyTracksView view = new MyTracksView();
view.DataContext = viewModel;
view.Show();
}
catch (Exception ex)
{
Log.Error("xxxxx", "Error on button click: " + ex.Message + Environment.NewLine + ex.InnerException);
}
}
In Outlook, if I click the button to open this view, I see the memory usage of Outlook.exe increase by 10mb (the window and it's accompanying data). When I close the view, none of that memory is reclaimed. If I click the button again, another 10mb is added, and again, none is released when I close the view.
I thought that this is because I'm creating a new viewmodel everytime, so I added some code to check if it was already instantiated (the view and viewmodel are now registered at the class level, rather that within the method, so that I don't create a new one each time) - _allTracksVM is an instantiation of AllTracksViewModel. _allTracksV is the view.
public void OnViewAllTracksClick(Office.IRibbonControl control)
{
try {
if (_allTracksVM == null)
{
_allTracksVM = new AllTracksViewModel();
}
_allTracksV = new AllTracksView();
_allTracksV.DataContext = _allTracksVM;
_allTracksV.Show();
}
catch (Exception ex)
{
Log.Error("xxxxx", "Error on button click: " + ex.Message + Environment.NewLine + ex.InnerException);
}
}
This didn't seem to make any difference. I then added an EventHandler that would fire when the view was closed:
_allTracksV.Closing += new System.ComponentModel.CancelEventHandler(this.view_RequestClose);
And this set both the objects to null (you can probably tell i'm grabbing at straws at this point):
void view_RequestClose(object sender, EventArgs e)
{
_allTracksVM = null;
_allTracksV = null;
}
The memory remains allocated. How can I dispose of the objects correctly (or perhaps I should be instantiating them differently), so that they don't consume another chunk of memory each time they are opened?
Thanks
Mick

Checking the VM for Null was a good idea but by setting it to null in the close handler, it is rendered useless :)
You could try to make the view a field in the containing class rather than a local variable. That way you don't need to create a new view each time.
As for the memory usage, it seems to me that you're doing it right. Since the GC only collects when necessary, it will take some time until the memoryusage declines.

Related

Can the Help.ShowHelp API be modified to simply invoke the CHM file?

This is my primary way for displaying help topics from within my WinForm button click handlers:
Handler:
private void buttonHelp_Click(object sender, EventArgs e)
{
CutTools.DisplayHelpTopic(this, "create-new-viewport.htm");
}
Base method:
public static void DisplayHelpTopic(Control parent, string topic)
{
try
{
// Use an empty form as the parent so that the help file will not block the CAD software
Form mHelpParent = new Form();
// Use location of this DLL file
System.Reflection.Module mod = parent.GetType().Module;
string path = Path.GetDirectoryName(mod.FullyQualifiedName);
Help.ShowHelp(mHelpParent,
Path.Combine(path, "cut-tools-help.chm"), HelpNavigator.Topic, topic);
}
catch (System.Exception ex)
{
_AcAp.Application.ShowAlertDialog(
string.Format("\nError: {0}\nStackTrace: {1}", ex.Message, ex.StackTrace));
}
}
The forms are displaid inside AutoCAD, BricsCAD or ZWCAD. The about is fine and great. But if I want to simply display the CHM file itself (so no actual form is available) I have to do this:
[CommandMethod("TS_DisplayHelp")]
public void TS_DisplayHelp()
{
// Use location of this DLL file
System.Reflection.Module mod = GetType().Module;
System.Diagnostics.Process.Start(
Path.Combine(Path.GetDirectoryName(mod.FullyQualifiedName), "cut-tools-help.chm"));
}
It works but has one drawback. It spawns a new instance of the help and does not use the same instance.
For example:
You start one of the other commands and show the help via button click. You cancel.
You start a different command and show the help via button click. Help.ShowHelp uses same instance.
You can command and start help via TS_DISPLAYHELP and it starts new instance.
Given the context of TS_DISPLAYHELP I can't work out how to directly use Help.ShowHelp as I can in my button click handlers.
At the moment I have managed to get around this issue by duplicating the DisplayHelpTopic code directly in the command TS_DISPLAYHELP method:
[CommandMethod("TS_DisplayHelp")]
public void TS_DisplayHelp()
{
try
{
// Use an empty form as the parent so that the help file will not block the CAD software
Form mHelpParent = new Form();
// Use location of this DLL file
System.Reflection.Module mod = GetType().Module;
string path = Path.GetDirectoryName(mod.FullyQualifiedName);
Help.ShowHelp(mHelpParent,
Path.Combine(path, "cut-tools-help.chm"), HelpNavigator.Topic, "command-index.htm");
}
catch (System.Exception ex)
{
_AcAp.Application.ShowAlertDialog(
string.Format("\nError: {0}\nStackTrace: {1}", ex.Message, ex.StackTrace));
}
}
I know that my default topic is "command-index.htm".
I am happy with the above resolution.

UWP multiple views not closing

PROBLEM
I am using a secondary view to run my media files, but When I close my secondary view with close button on it ( while media is still playing ) the secondary view/window closes but the media somehow keeps playing because I can hear the sound and source of sound seems to be the primary view ( main app window ). how can I completely terminate the secondary window when I close it?
TRIED
I followed windows samples multiple views and was able to complete all steps, I copied the ViewLifetimeControl.cs file from the sample and used it in my project. the code runs fine until it reaches Windows.Current.Close() in released event of the secondary view.
Then it gives an exception when it tries "Window.Current.Close()" with in the released event. according to documentation exception occurs due to any on going changes ( which might be because of media file playing ), but I need to force close the window even when media file is playing how can I do that? btw here is the exception :
Message = "COM object that has been separated from its underlying RCW cannot be used."
Code to Create and Show secondary view
internal static async Task CompactOpen(string Title, string caption)
{
ViewLifetimeControl viewControl = null;
await CoreApplication.CreateNewView().Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
viewControl = ViewLifetimeControl.CreateForCurrentView();
viewControl.Title = Title;
viewControl.StartViewInUse();
var frame = new Frame();
frame.MinHeight = 200;
frame.MinWidth = 200;
frame.Navigate(typeof(CompactNowPlayingPage), new object[] { viewControl,caption});
Window.Current.Content = frame;
Window.Current.Activate();
ApplicationView.GetForCurrentView().Title = viewControl.Title;
});
((App)App.Current).SecondaryViews.Add(viewControl);
var selectedView = viewControl;
var sizePreference = new SizePreferenceString() { Title = "SizePreference", Preference = ViewSizePreference.Default };
var anchorSizePreference = new SizePreferenceString() { Title = "AnchorSizePreference", Preference = ViewSizePreference.Default };
if (selectedView != null && sizePreference != null && anchorSizePreference != null)
{
try
{
selectedView.StartViewInUse();
var viewShown = await ApplicationViewSwitcher.TryShowAsStandaloneAsync(
selectedView.Id,
sizePreference.Preference,
ApplicationView.GetForCurrentView().Id,
anchorSizePreference.Preference);
if (!viewShown)
{
// The window wasn't actually shown, so release the reference to it
// This may trigger the window to be destroyed
}
// Signal that switching has completed and let the view close
selectedView.StopViewInUse();
}
catch (InvalidOperationException)
{
// The view could be in the process of closing, and
// this thread just hasn't updated. As part of being closed,
// this thread will be informed to clean up its list of
// views (see SecondaryViewPage.xaml.cs)
}
}
}
Released Event
private async void ViewLifetimeControl_Released(Object sender, EventArgs e)
{
((ViewLifetimeControl)sender).Released -= ViewLifetimeControl_Released;
// The ViewLifetimeControl object is bound to UI elements on the main thread
// So, the object must be removed from that thread
await mainDispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
((App)App.Current).SecondaryViews.Remove(thisViewControl);
});
// The released event is fired on the thread of the window
// it pertains to.
//
// It's important to make sure no work is scheduled on this thread
// after it starts to close (no data binding changes, no changes to
// XAML, creating new objects in destructors, etc.) since
// that will throw exceptions
Window.Current.Close(); //this is where that exception occurs
}
Note : both of above methods and even all the related variables, all of them I have followed the guidelines within the uwp sample for multiple views.
Thanks in advance, any help would be really appreciated, I only want to force close the secondary view ( If that's possible )
Is this in the editor or the app? If it's in your debug or build of the app, the secondary view is most likely still open but hidden. You may be using a custom close button which doesn't perform its job well enough. Instead of putting down SecondaryViews.Remove you should do what you had originally written and try StopViewInUse. It may not work, I'm not used to this kind of thing.

Multi Threading WPF C#

Still learning MVVM and multi-threading and have come across something I cannot figure out.
Users access a list of applicants via a Datagrid for their specific branch location. They can click a button within the datagrid to obtain SelectedItem to open a more detailed view of that application.
The initial time they open this view, the data is correct; however, any further time(s) they open the view the original data is still shown. It does not show the second applicants information in the View unless the application is closed and opened again.
I have read that the ViewModel is stored in memory and I thought, maybe threading would provide a solution. I used the following code to attempt just that; however, no luck.
Can someone please point me in the correct direction on how I can handle this situation, or if I am approaching this totally wrong. :)
Thank you for your time.
private void NewWindowHandler(object sender, RoutedEventArgs e)
{
try
{
Craig_Tools.Data.Globals.selectedAppID = Convert.ToInt32(appIDTextBox.Text);
Thread newWindowThread = new Thread(delegate()
{
Window tempWindow = new Window
{
Title = "Display Applicant",
Content = new Craig_Tools.ApplicationTrackingSystem.userControls.atDisplay(),
SizeToContent = SizeToContent.WidthAndHeight,
//ResizeMode = ResizeMode.NoResize
};
tempWindow.Show();
System.Windows.Threading.Dispatcher.Run();
});
newWindowThread.SetApartmentState(ApartmentState.STA);
newWindowThread.Start();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), "Error opening Applicant Display");
}
}
I have also tried the following.
private void NewWindowHandler(object sender, RoutedEventArgs e)
{
try
{
Craig_Tools.Data.Globals.selectedAppID = Convert.ToInt32(appIDTextBox.Text);
//create and show window
atDisplay newView = new atDisplay();
ViewModels.displayViewModel viewModel = new ViewModels.displayViewModel();
newView.DataContext = viewModel;
newView.Title = "Applicant ID: " + Craig_Tools.Data.Globals.selectedAppID;
newView.Show();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString(), "Error opening Applicant Display");
}
}
What I have figured out using Visual Studio debugger is that the ViewModel is never executed when attempting to open the information for the second applicant. It seems to simply just grab the data that is in memory.

How to avoid multiple event fire when WPF page is opened again?

I have a frame and few pages in my WPF application.
My navigation is controlled by buttons. On each button I have click handler that creates new page with some parameters:
private void ButtonProductionAuto_OnClick(ref TechModbus, RoutedEventArgs e)
{
FrameMain.Content = new PageProductionAuto(someobject, this);
}
private void ButtonProductionManual_OnClick(ref TechModbus, RoutedEventArgs e)
{
FrameMain.Content = new PageProductionManual(someobject, this);
}
When I'm switching between pages - previous pages still exist in memory and they react on some custom events.
(edit)
This is my code related with events:
public PageProductionAuto(ref TechModbus modbus, MainWindow wnd)
{
// ...
wnd.KeyDown += Wnd_KeyDown;
wnd.KeyUp += Wnd_KeyUp;
m.OnReadFinished += Modbus_OnReadFinished;
// ...
}
How can I dispose these pages or how can I avoid double-fire on my events when page is opened second time?
You should unregister the events on leaving the page.
GarbageCollector will then "dispose" (it's actually not a dispose) by itsself when there are no more references on those objects(PageProductionAuto and PageProductionManual).
Quoting MS:
The reason WPF controls don't implement IDisposable is because they have nothing to dispose. They have no handle to clean up, and no unmanaged resources to release. To ensure your memory is cleaned up, just make sure nothing has a reference to the controls once you're finished with them.
Your question is incomplete. But I can answer the question about "how to avoid multiple instances" part of it. To dispose your pages, you have to detach your events, remove them from the "openedPages" collection, and dispose where possible.
List<object> openedPages = new List<object>();
private void ButtonProductionAuto_OnClick(object sender, RoutedEventArgs e)
{
var page = openedPages.FirstOrDefault(p => p.GetType().Equals(typeof(PageProductionAuto)));
if(page == null)
{
page = new PageProductionAuto(someobject, this);
opendPages.Add(page);
}
else
{
page.SetObjects(someobject, this); // create a method to set "someObject" to your page.
}
FrameMain.Content = page;
}
What I did to avoid this was I have a window with 2 frames in it. I placed one of each source XAML in each frame so frame 1 was XAML 1 and frame 2 was XAML 2. Then I just edited the visibility to collapsed and visible. Then you don't have any page changes or instances getting created. You just create the original 2 instances.

Try-catch not working loading XAML resource

I have these pieces of code:
private void btnPlanning_Click(object sender, RoutedEventArgs e)
{
LoadPage("PlanningView.xaml");
}
private void LoadPage(string APage)
{
try
{
frameMainView.Source = new Uri(APage, UriKind.Relative);
}
catch (Exception ex)
{
string errorString = $"Resource <{APage}> not found! ";
DoLogD(errorString + " " + ex.Message);
MessageBox.Show(errorString);
}
}
Clicking on btnPlanning button, LoadPage is called passing a string with the name of the XAML resource I want to load in the frame control frameMainView.
If the given resource doesn't not exist, I would like to catch the exception and inform the user.
The problem is that when i click the button (and the resource doesn't exist), I get in any case
PresentationFramework.pdb not loaded
and a internal System.IO.IOException telling me the resource is not available.
Why my try-catch block is not working?
there are many ways to load the pages into the frame:
By setting the source
frameMainView.Source = new Uri("PlanningView.xaml",UriKind.RelativeOrAbsolute);
By setting the Content:
frameMainView.Content= new PlanningView();
By using the NavigationService:
frameMainView.NavigationService.Navigate(new PlanningView());
It´s a user interface initialization Problem. Can you get more information from the visual Studio "Output" Window?

Categories

Resources