0

I built a Prism application using WPF, .Net 4, Prism 4.1, and Unity. I'm using a DirectoryModuleCatalog to find modules at runtime. My views are displayed in a TabControl (MainRegion). When I remove a view from the region, the view and viewmodel remain in memory and never get garbage collected - the tabitem is removed. After many hours searching, I cannot figure out what I'm doing wrong.

Here's my bootstrapper:

public class Bootstrapper : UnityBootstrapper
{
    protected override void InitializeShell()
    {
        base.InitializeShell();
        App.Current.MainWindow = (Window)Shell;
        App.Current.MainWindow.Show();
    }

    protected override DependencyObject CreateShell()
    {
        var shell = new Shell();
        return shell;
    }

    protected override IModuleCatalog CreateModuleCatalog()
    {
        return new DirectoryModuleCatalog() { ModulePath = @".\Modules" };
    }
}

Here's my module:

[Module(ModuleName = "ModuleA")]
public class Module : IModule
{
    private IRegionManager _regionManager;

    public Module(IRegionManager regionManager)
    {
        _regionManager = regionManager;
    }

    public void Initialize()
    {
        var view = new UserControl1();
        //_regionManager.RegisterViewWithRegion("MainRegion", typeof(UserControl1));
        _regionManager.Regions["MainRegion"].Add(view, "ModuleA");
        _regionManager.Regions["MainRegion"].Activate(view);
    }
}

And heres the viewmodel for my view that gets added to the region:

public class ViewModel
{
    public DelegateCommand RemoveView { get; set; }

    public ViewModel()
    {
        RemoveView = new DelegateCommand(() =>
            {
                var regionManager = ServiceLocator.Current.GetInstance<IRegionManager>();
                var view = regionManager.Regions["MainRegion"].GetView("ModuleA");
                regionManager.Regions["MainRegion"].Deactivate(view);
                regionManager.Regions["MainRegion"].Remove(view);
            });
    }
}

And here's the code behind for the view:

public partial class UserControl1 : UserControl
{
    public UserControl1()
    {
        InitializeComponent();

        this.DataContext = new ViewModel();
    }
}

I've read that it could be because I'm instantiating the view in the module or perhaps the viewmodel in the view? When I use Red Gate Memory Profiler, and remove the view via the DelegateCommand, the view and viewmodel are both flagged as not being able to be garbage collected. Where is the reference that I'm not properly cutting?

Heres the retention graph from Ants: https://docs.google.com/file/d/0B4XjO9pUQxBXbGFHS1luNUtyOTg/edit?usp=sharing

Here's a test solution showing the issue.

Also, I posted the question on CodePlex as well.

Chris Klepeis
  • 9,783
  • 16
  • 83
  • 149
  • Wild stab in the dark: http://stackoverflow.com/questions/516617/what-is-the-weak-event-pattern-used-in-wpf-applications – ta.speot.is Feb 24 '13 at 06:26
  • 1
    Hey, Chris. I looked at your project. It looks like your ViewModel implements IDisposable. I've found this can have some strange effects on lifetime if Dispose() is not called (WPF, unlike WinForms, doesn't make much use of IDisposable). IDisposable is good for things like closing unmanaged resources like DB connections, etc, but there are better methods for this. Try taking it out and see what happens. – Anderson Imes Feb 28 '13 at 18:15
  • The other thing I should mention is that you should make sure you are a good measurement for the lifetime of your object. Collection is not very deterministic and I found in WPF apps that it could sometimes take a while for an object to be collected. Your graph in ANTs looks ok - the things that have references to your viewmodel all look to have a WeakReference break in there somewhere, so they shouldn't be pinning the object. – Anderson Imes Feb 28 '13 at 18:18
  • The binding reference is a strong one, but that shouldn't be a problem because your View will get collected, then collecting your ViewModel. Have you checked the lifetime of your View? The lifetime of your `UserControl1` and `ViewModel` should be the same. – Anderson Imes Feb 28 '13 at 18:20
  • @Anderson - thank you for taking a look into this. In addition to the resolution I listed below, we have some cleanup to do on bindings to objects that are not implementing INotifyPropertyChanged. – Chris Klepeis Mar 01 '13 at 13:51

2 Answers2

0

It looks like you have a binding reference to it still in your retention graph.

Read and understand the following:

A memory leak may occur when you use data binding in Windows Presentation Foundation

I think it may be your problem, but you didn't show your actual bindings.

Alan
  • 7,875
  • 1
  • 28
  • 48
  • Hi Alan, yes it does indeed appear to be from a binding somewhere. I created a test solution here where my module only binds (via INotifyPropertyChanged) to 1 textbox, but the view and viewmodel in the module remain in memory. https://docs.google.com/file/d/0B4XjO9pUQxBXVEtJaW8yYWV1SGs/edit?usp=sharing . The problem is finding what binding is getting goofed up – Chris Klepeis Feb 27 '13 at 15:16
  • In my sample app there are two bindings. A string property to a textboxs Text, and a DelegateCommand to the Command of a button. The object that holds these properties implements INotifyPropertyChanged. Even when I set the TextBox binding to OneTime, the objects remain in memory. In our actual application there are tons of bindings (MVVM and viewmodelfirst via DataTemplates) so clearing out all of those bindings is not an option. – Chris Klepeis Feb 27 '13 at 16:00
0

Finally found the root cause of my problem....

In our Shell.xaml we were binding IsDefault in one of our buttons to a PasswordBox's IsKeyboardFocused:

<Button Style="{DynamicResource RedSubmitButtonStyle}" IsDefault="{Binding ElementName=passwordBox1, Path=IsKeyboardFocused}" Command="{Binding LoginCommand}" Content="Login" Height="23" HorizontalAlignment="Left" Margin="145,86,0,0" Name="button1" VerticalAlignment="Top" Width="75" />

IsKeyboardFocused, is a dependency property according to MSDN - so should be good on that end.

It was related to the attached property we had on the Password box that allows us to bind to the Password entered. The focus was remaining on that password box even after we hid the ChildWindow (from WPF toolkit extended). So instead of using IsDefault, I added a keydown event to the PasswordBox and if it was Key.Enter, I would change the focused UI control and log the person into the program.

Here's the full contents of our Shell.xaml file

<Grid x:Name="MainGrid" core:SharedResourceDictionary.MergedDictionaries="TabControlThemes;MenuThemes;ButtonThemes;DataGridThemes;TreeViewThemes;ComboBoxThemes;ListBoxThemes;GroupBoxThemes;ToggleSwitchThemes">

    <DockPanel>
        <ContentControl x:Name="menuContent" DockPanel.Dock="Top" prism:RegionManager.RegionName="MenuRegion" />
        <ContentControl DockPanel.Dock="Bottom" prism:RegionManager.RegionName="FooterRegion" />
        <ContentControl DockPanel.Dock="Top" prism:RegionManager.RegionName="MainContentRegion" />
    </DockPanel>

    <StackPanel Orientation="Horizontal" Panel.ZIndex="4" HorizontalAlignment="Right" VerticalAlignment="Top">
        <Button Visibility="{Binding IsFullScreenToggleVisible, Converter={StaticResource visConv}}" Command="{Binding ToggleFullScreen}" Height="50" Name="button4" Width="70" HorizontalAlignment="Right" Margin="0,10,10,0" VerticalAlignment="Top">
            <Button.Content>
                <TextBlock FontSize="12" FontWeight="Bold" TextWrapping="Wrap" TextAlignment="Center" HorizontalAlignment="Center" VerticalAlignment="Center" Text=" Toggle Full Screen" />
            </Button.Content>
        </Button>

        <Button Visibility="{Binding IsAppCloseButtonVisible, Converter={StaticResource visConv}}" Command="{Binding ShutdownApplication}" Height="50" Name="button3" Width="50" HorizontalAlignment="Right" Margin="0,10,10,0" VerticalAlignment="Top">
            <Button.Content>
                <Image Source="..\Graphics\close.png" Name="image1" />
            </Button.Content>
        </Button>
    </StackPanel>

    <xctk:ChildWindow Name="loginChildWindow" Panel.ZIndex="3" CloseButtonVisibility="Collapsed" FocusedElement="{Binding ElementName=usernameTextBox}" WindowStartupLocation="Center" WindowState="{Binding IsVisible, Mode=TwoWay, Converter={StaticResource boolConverter}}" IsModal="True" OverlayOpacity="1" Caption="Pioneer Login" Height="164" Width="261">
        <xctk:ChildWindow.OverlayBrush>
            <ImageBrush Stretch="None" Viewport="0,0,46,29" ViewportUnits="Absolute" ImageSource="../Graphics/escheresque.png" TileMode="Tile" />
        </xctk:ChildWindow.OverlayBrush>
        <xctk:BusyIndicator IsBusy="{Binding IsLoginBusy}" BusyContent="Authenticating...">
            <Grid>
                <TextBox GotFocus="usernameTextBox_GotFocus" Text="{Binding Username, UpdateSourceTrigger=PropertyChanged}" Height="23" HorizontalAlignment="Left" Margin="99,20,0,0" Name="usernameTextBox" VerticalAlignment="Top" Width="120">
                    <TextBox.InputBindings>
                        <KeyBinding Key="Enter" Command="{Binding LoginCommand}" />
                    </TextBox.InputBindings>
                </TextBox>
                <Label Content="Username" Height="28" HorizontalAlignment="Left" Margin="28,18,0,0" Name="label1" VerticalAlignment="Top" />
                <Label Content="Password" Height="28" HorizontalAlignment="Left" Margin="29,47,0,0" Name="label2" VerticalAlignment="Top" />
                <PasswordBox attach:PasswordBoxAssistant.BindPassword="True" attach:PasswordBoxAssistant.BoundPassword="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Height="23" HorizontalAlignment="Left" Margin="100,50,0,0" Name="passwordBox1" VerticalAlignment="Top" Width="120" />
                <Button Style="{DynamicResource RedSubmitButtonStyle}" IsDefault="{Binding ElementName=passwordBox1, Path=IsKeyboardFocused}" Command="{Binding LoginCommand}" Content="Login" Height="23" HorizontalAlignment="Left" Margin="145,86,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
                <Button Style="{DynamicResource RedSubmitButtonStyle}" Command="{Binding LazyLoginCommand}" Visibility="{Binding IsDebugMode, Converter={StaticResource visConv}}" Content="Quick Login" Height="23" HorizontalAlignment="Left" Margin="23,87,0,0" Name="button2" VerticalAlignment="Top" Width="89" />
            </Grid>
        </xctk:BusyIndicator>
    </xctk:ChildWindow>

</Grid>
Chris Klepeis
  • 9,783
  • 16
  • 83
  • 149