2

我遇到了 WPF 中组合框的问题,它们似乎挂在打开的第一个 DataContext 上。当我更改 ComboBox 上的 DataContext 时,子 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

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

埃蒙

4

2 回答 2

0

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

我在这里发布了代码:https ://github.com/EamonHetherton/Demos/blob/master/StackOverflow/18096050/StopComboBoxMemoryLeakBehaviour.cs

警告购买者:这种解决方案依赖于反思和可能带来的脆弱性。它对我有用,YMMV。

于 2017-02-09T05:18:32.683 回答
0

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

我发现 Popup / ComboBox 的问题本质上是“_popupRoot”的 DataContext 在其父级的 DataContext 设置为 null 后没有释放。

对于 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) { }
    }
}
于 2019-03-14T17:50:46.677 回答