1

I am writing an application in WPF. I have a setup similar to that described by Sheridan in https://stackoverflow.com/a/19654812.

Specifically I have a listview that has a select command that sets the Mainview property on my RootViewModel to that of the selected entry. That is displayed in the following XAML

<DockPanel>
    <UserControl DockPanel.Dock="Top" Content="{Binding MenuView,Mode=OneWay}"/>
    <Border DockPanel.Dock="Top" BorderThickness="0 2 0 0" BorderBrush="{StaticResource GarminDarkBrush}"  Panel.ZIndex="1001">
        <DockPanel LastChildFill="True">
            <UserControl DockPanel.Dock="Left" Content="{Binding ExpandableLeftPanel,Mode=OneWay}"   />
            <UserControl Content="{Binding MainView,Mode=OneWay}"   Panel.ZIndex="0" />
        </DockPanel>
    </Border>
</DockPanel>

And here is a portion of that RootViewModel

public class RootViewModel : ViewModelBase
{
    public virtual MenuViewModel MenuView { get; set; }
    public virtual ViewModelBase MainView { get; set; }
}

This works for displaying different entries.

Now to my problem. In each of the viewmodels I display there is a child ConsoleConstrollerViewModel that has a ConsoleViewModel that when displayed registers an AttachedBehavior to a RichTextBox. When I select each item in my list the proper view is displayed and the ConsoleViewModel registers and behaves correctly. However, when I switch between the views that behavior is never unregistered. This causes a problem because whenever I switch views a new RichTextBox is created(by WPF not me) and bound to my behavior. Which would normally cause a memory leak because I attach event handlers to the RTB. I fixed this memory leak by using a dictionary of ConsoleViewModel and swapping out the RTB associated with the ConsoleViewModel via the ConsoleMediator class.

I would still like to understand how to properly unregister the RTB so that when swapping between views I can unregister other event handlers on the ConsoleViewModel that wastes CPU changing the RTB that is not displayed and will be replaced when viewed anyways.

Portion of the ConsoleControllerViewModel

public class ConsoleControllerViewModel : ViewModelBase
{
    public virtual ConsoleViewModel Console { get; set; }
}

Here is the attached behavior

public static class ConsoleTextBoxBehavior
{
    public static readonly DependencyProperty ConsoleBindingProperty =
                   DependencyProperty.RegisterAttached("Console",
                   typeof(ConsoleViewModel), typeof(ConsoleTextBoxBehavior),
                   new UIPropertyMetadata(null, OnConsoleBound));

    public static ConsoleViewModel GetConsole(DependencyObject obj)
    {
        return (ConsoleViewModel)obj.GetValue(ConsoleBindingProperty);
    }

    public static void SetConsole(DependencyObject obj, ConsoleViewModel value)
    {
        obj.SetValue(ConsoleBindingProperty, value);
    }

    private readonly static Dictionary<ConsoleViewModel, ConsoleMediator> _registeredConsoles =
        new Dictionary<ConsoleViewModel, ConsoleMediator>();

    private static void OnConsoleBound(object sender, DependencyPropertyChangedEventArgs e)
    {
        var textBox = sender as RichTextBox;
        if(textBox == null)
        {
            throw new ArgumentException("Can only bind to RichTextBoxes");
        }
        var c = e.OldValue as ConsoleViewModel;
        if (e.OldValue != null && _registeredConsoles.ContainsKey(c))
        {
            // never hit
            var cm = _registeredConsoles[c];
            cm.UnhookConsole();
            _registeredConsoles.Remove(c);
        }
        c = e.NewValue as ConsoleViewModel;
        if (c != null)
        {
            HookUpConsole(textBox, c);
        }
    }

    //other functions left out for brevity
}

Here it is attached to RTB. Please Note that {Binding .} works because This is from the context of a ConsoleViewModel not a ConsoleControllerViewModel.

<RichTextBox Name="ConsoleOutput"
             ext:ConsoleTextBoxBehavior.Console="{Binding .}"
             IsReadOnly="True"
             >

I understand that OnConsoleBound should be called when notified of that particular property changing. In my particular application I never need that ConsoleViewModel to change with respect to its parent. So do I have to hack up my viewmodel to listen for events on the rootview to determine if it is the one that is actually being displayed? And then switch that Viewmodel out into a temporary place and then put it back when displayed? Seems ugly to me so i wanted to ask the peanut gallery for another solution before making a mess that is hard to untangle.

Also, ViewModelBase is a INotifyPropertyChanged and I have some magic(Dynamic Proxy) to raise those events for virtual members. So please don't get hung up on not seeing property changed events being raised.

Community
  • 1
  • 1
Glen Nicol
  • 275
  • 1
  • 3
  • 15

0 Answers0