WPF Combobox "泄漏"内存

3
我在WPF中遇到了一个组合框的问题,它们似乎会保留它们最初打开时使用的第一个DataContext。当我更改ComboBox上下文时,子PopupRoot对象仍然引用旧的DataContext。
起初,我以为我们做错了什么,但是我无法确定具体情况,因此我尝试简化。我已经成功地在一个非常简单的形式中重新创建了我们应用程序中看到的行为,因此它似乎更像是WPF ComboBox实现中的错误。这听起来有些争议性,所以我想向stackoverflow寻求帮助。
以下是示例的核心代码:
<Window x:Class="ComboBoxTest.MainWindow" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="150" Width="525">
    <DockPanel>
        <Button Click="ReloadModel" Width="137" Height="40">Reload Model</Button>
        <ComboBox Name="ComboBox" 
            ItemsSource="{Binding AvailableOptions}" 
            SelectedItem="{Binding SelectedOption}" 
            Width="235" Height="43">
        </ComboBox>
    </DockPanel>
</Window>

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        var newModel = new ViewModel();
        ComboBox.DataContext = newModel;
    }

    private void ReloadModel(object sender, RoutedEventArgs e)
    {        
        var newModel = new ViewModel();
        ComboBox.DataContext = newModel;
    }
}

public class ViewModel : INotifyPropertyChanged
{
    public ViewModel()
        : this(new[] { "Option 1", "Option 2", "Option 3" })
    { }

    public ViewModel(IEnumerable<string> options)
    {
        _selectedOption = options.First();
        _availableOptions = new ObservableCollection<string>(options);
    }

    protected void RaisePropertyChanged(string propertyName)
    {
        var propertyChangedHandler = PropertyChanged;
        if (propertyChangedHandler != null)
        {
            propertyChangedHandler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;

    private readonly ObservableCollection<string> _availableOptions;
    public ObservableCollection<string> AvailableOptions
    {
        get
        {
            return _availableOptions;
        }
    }

    private string _selectedOption;
    public string SelectedOption
    {
        get { return _selectedOption; }
        set
        {
            if (_selectedOption == value)
            {
                return;
            }
            _selectedOption = value;
            RaisePropertyChanged("SelectedOption");
        }
    }
}

重现步骤:
1)运行应用程序
2)打开下拉框(以便呈现下拉选项)
3)单击“重新加载模型”按钮

此时将有两个ViewModel对象,旧的、意外的实例如下所示: ViewModel->PopupRoot->Popup->ComboBox->MainWindow->App

这是一个错误还是我的操作有误?

Eamon


你能否在reload方法中加入计时器,让它重复执行数小时并观察是否真的存在内存泄漏?或者只是垃圾回收需要追赶进度吗? - Gayot Fow
这并不是真正意义上的泄漏,因为它是有界限的,但第一个ViewModel对象永远不会被回收,无论您点击“重新加载模型”多少次,它都会被PopupRoot对象保留。在下一个ViewModel加载后,所有随后创建的ViewModel都会被回收。 - Eamon
谢谢澄清。这不是一个失控的泄漏,而是一个“孤立”的数据上下文。在您的MainWindow构造函数中,您可以将最后两行替换为对reload方法的单个调用吗? - Gayot Fow
这没有任何区别。似乎当PopupRoot首次打开时,它会继承ComboBox的DataContext,之后当ComboBox的DataContext更改时,PopupRoot不再接收更新。我发现另一个问题也表现出类似的行为,其中菜单是罪魁祸首(尽管PopupRoot也是)http://stackoverflow.com/q/17239406/718033 - Eamon
你有没有找到解决办法?弹出窗口和上下文菜单一直保留对所有ViewModel和View的引用,导致我遇到了巨大的内存问题...这使得我的整个应用程序基本上无法进行垃圾回收! - Joe
嗨乔,看看我刚刚发布的答案。 - Eamon
2个回答

1

0

最近我遇到了几个与Popup / ContextMenu / ComboBox绑定DataContext相关的内存泄漏问题。

我发现,对于Popup / ComboBox来说,本质上问题在于"_popupRoot"的DataContext在其父级的DataContext设置为null后没有被释放。

对于ContextMenu,如果它与某些生成控件的ItemsSource绑定一起使用,则WPF将缓存ContextMenu,因此除非用户右键单击以再次弹出ContextMenu,否则其DataContext将不会被释放。

我成功创建了3个派生类来替换使用DataContext绑定的WPF控件。我将它们粘贴在这里,希望它们对其他人有用。

public class ComboBoxFixMem : ComboBox
{
    public ComboBoxFixMem()
    {
        this.DataContextChanged += ComboBox_DataContextChanged;
    }

    private void ComboBox_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        if (this.DataContext != null)
            return;
        FrameworkElement fe = this.GetTemplateChild("PART_Popup") as FrameworkElement;
        if (null != fe)
            fe.DataContext = null;
        PopupFixMem.ClearPopupDataContext(fe as Popup);
    }
}

public class ContextMenuFixMem : ContextMenu
{
    protected override void OnClosed(RoutedEventArgs e)
    {
        base.OnClosed(e);
        FrameworkElement p = this.Parent as FrameworkElement;
        if (null != p)
            p.DataContext = null;
    }
}

public class PopupFixMem : Popup
{
    public PopupFixMem()
    {
        this.DataContextChanged += Popup_DataContextChanged;
    }

    private void Popup_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        if (this.DataContext != null)
            return;
        ClearPopupDataContext(this);
    }

    public static void ClearPopupDataContext(Popup popup)
    {
        if (null == popup)
            return;
        try
        {
            var fiPopupRoot = typeof(Popup).GetField("_popupRoot", BindingFlags.NonPublic | BindingFlags.Instance);
            var popupRootWrapper = fiPopupRoot?.GetValue(popup);
            if (null == popupRootWrapper)
                return;
            var valueFieldInfo = popupRootWrapper.GetType().GetProperty("Value", BindingFlags.NonPublic | BindingFlags.Instance);
            var popupRoot = valueFieldInfo?.GetValue(popupRootWrapper, new object[0]) as FrameworkElement;
            if (null != popupRoot)
                popupRoot.DataContext = null;
        }
        catch (Exception) { }
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接