C# WPF组合框“;“泄漏”;记忆

C# WPF组合框“;“泄漏”;记忆,c#,wpf,memory-leaks,combobox,C#,Wpf,Memory Leaks,Combobox,我在WPF中遇到了一个组合框问题,它们似乎挂在打开它们时使用的第一个DataContext上。当我在ComboBox上更改DataContext时,子PopuRoot对象仍然引用旧的DataContext 起初我以为我们做错了什么,但我很难弄清楚那可能是什么,所以我试着简化。我已经设法以一种非常简单的形式重新创建了我在应用程序中看到的行为,因此它看起来更像是WPF ComboBox实现中的一个bug。这听起来有点争议,所以我想我应该求助于stackoverflow 示例的核心代码如下所示: &l

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

起初我以为我们做错了什么,但我很难弄清楚那可能是什么,所以我试着简化。我已经设法以一种非常简单的形式重新创建了我在应用程序中看到的行为,因此它看起来更像是WPF ComboBox实现中的一个bug。这听起来有点争议,所以我想我应该求助于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");
        }
    }
}

重新加载模型
公共部分类主窗口:窗口
{
公共主窗口()
{
初始化组件();
var newModel=newviewmodel();
ComboBox.DataContext=newModel;
}
私有void重载模型(对象发送方,RoutedEventArgs e)
{        
var newModel=newviewmodel();
ComboBox.DataContext=newModel;
}
}
公共类视图模型:INotifyPropertyChanged
{
公共视图模型()
:这(新的[]{“选项1”、“选项2”、“选项3”})
{ }
公共视图模型(IEnumerable选项)
{
_selectedOption=options.First();
_availableOptions=新的ObservableCollection(选项);
}
受保护的void RaisePropertyChanged(字符串propertyName)
{
var propertyChangedHandler=PropertyChanged;
if(propertyChangedHandler!=null)
{
propertyChangedHandler(这是新的PropertyChangedEventArgs(propertyName));
}
}
公共事件属性更改事件处理程序属性更改;
私有只读可观察集合\u可用选项;
公共可见收集可用选项
{
得到
{
返回可用选项;
}
}
私有字符串_selectedOption;
公共字符串选择选项
{
获取{return\u selectedOption;}
设置
{
如果(_selectedOption==值)
{
返回;
}
_选择选项=值;
RaisePropertyChanged(“SelectedOption”);
}
}
}
复制步骤:
1) 运行应用程序
2) 打开组合框(以便呈现下拉选项)
3) 单击“重新加载模型”按钮

此时将有两个ViewModel对象,较旧的、意外的实例如下所示: 视图模型->弹出窗口->弹出窗口->组合框->主窗口->应用程序

这是一个错误还是我做错了


伊蒙

乔的评论把我的注意力带回了这个老问题,我已经解决了这个老问题,供自己使用。最后,我编写了一个行为,可以附加到处理内存泄漏的组合框

我在这里发布了代码:


买主警告:这个解决方案依赖于反思和可能带来的脆弱性。它适用于我,YMMV。

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

我发现Popup/ComboBox的主要问题是在其父对象的DataContext设置为null后,“_popupRoot”的DataContext没有被释放

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

我设法创建了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) { }
    }
}

你能用定时器来测试你的重新加载方法,让它重复几个小时,看看它是否真的泄漏了?或者只是GC需要被捕获?它不是真正意义上的泄漏,因为它是有界的,但是第一个ViewModel对象永远不会被收集,无论您单击多少次“重新加载模型”,它都会被PopuRoot对象扎根。所有随后创建的ViewModel都会在加载下一个ViewModel后被收集。感谢您的澄清。这不是一个失控的泄漏,而是一个“孤立”的数据上下文。在主窗口构造函数中,请用对重载方法的单个调用替换最后两行,这没有什么区别。PopuRoot似乎在第一次打开时继承了组合框的DataContext,之后当组合框的DataContext更改时,它不再接收更新。我发现了另一个问题,它在菜单上表现出类似的行为(Popuroot也是罪魁祸首),你有没有找到解决方法?我有巨大的内存问题,因为弹出窗口和上下文菜单保留了对我所有视图模型和视图的引用。。。它使我的整个应用程序基本上没有垃圾收集!