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

查看:22
本文介绍了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.

示例的核心代码如下:

<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?

埃蒙

推荐答案

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

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

我发现 Popup/ComboBox 的本质问题是_popupRoot"的 DataContext 在其父项的 DataContext 设置为 null 后没有释放.

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,所以它的DataContext不会被释放,除非用户在某处再次右键弹出ContextMenu.

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天全站免登陆