WPF组合框“泄漏"记忆 [英] WPF Combobox "leaks" memory

查看:71
本文介绍了WPF组合框“泄漏"记忆的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了WPF中组合框的问题,它们似乎挂在打开它们的第一个DataContext上.当我在ComboBox上更改DataContext时,子PopupRoot对象仍引用旧的DataContext.

I have run into an issue with combo boxes in WPF where they seem to hang onto the first DataContext they were opened with. When I change the DataContext on my ComboBox, a child PopupRoot object still references the old DataContext.

起初,我以为我们做错了什么,但是我在弄清楚可能是什么时遇到了麻烦,因此我尝试简化.我已经设法以非常简单的形式重新创建了在应用程序中看到的行为,因此它看起来更像是WPF ComboBox实现中的错误.这听起来有点争议,所以我想我应该转向stackoverflow寻求帮助.

At first I assumed we were doing something wrong but I was having trouble working out what that might be so I tried to simplify. I have managed to recreate the behavior I am seeing in our application in a very simple form so it seems more like a bug in the WPF ComboBox implementation. That sounds a little controversial so I thought I'd turn to stackoverflow for help.

该示例的核心代码如下:

The core code for the sample is below:

<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)点击重新加载模型"按钮

Steps to reproduce:
1) Run Application
2) Open Combobox (so that it renders the drop down options)
3) Click "Reload Model" button

这时将有两个ViewModel对象,较早的,意外的实例的植根如下: ViewModel-> PopupRoot-> Popup-> ComboBox-> MainWindow-> App

At this point there will be be two ViewModel objects, the older, unexpected instance is rooted like: ViewModel->PopupRoot->Popup->ComboBox->MainWindow->App

这是错误还是我做错了?

Is this a bug or am I doing it wrong?

Eamon

推荐答案

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

Recently I encountered several memory leak problems which were related to Popup / ContextMenu / ComboBox binding with DataContext.

我发现,实质上Popup/ComboBox的问题在于,将其父项的DataContext设置为null之后,"_ popupRoot"的DataContext没有被释放.

I found out that essentially the problem for Popup / ComboBox was that the "_popupRoot"'s DataContext was not released after the DataContext of its parents were set to null.

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

For ContextMenu, if it's used with some kind of ItemsSource binding generated controls, then WPF will cache the Contextmenu, so its DataContext will not be released unless the user right click to pop up the ContextMenu somewhere again.

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

I managed to create 3 derived classes to replace the WPF controls where DataContext binding was used. I will paste them here, hopefully, they may be useful to someone else.

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) { }
    }
}

这篇关于WPF组合框“泄漏"记忆的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆