【问题标题】:Removing the reference to a user control in wpf删除对 wpf 中用户控件的引用
【发布时间】:2015-10-22 19:49:41
【问题描述】:

我有一个 wpf 应用程序,它有一个主窗口和菜单。这个主窗口有一个面板,在点击菜单项时,我创建了一个用户控件的实例并使用该控件加载面板。

<Window x:Class="MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="" MinHeight="750" Height="Auto" MinWidth="1100" Width="Auto" WindowState="Maximized" ScrollViewer.VerticalScrollBarVisibility="Auto"
        Loaded ="MainWindow_OnLoaded" Closing="Window_Closing">
    <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility ="Auto" SizeChanged="ScrollViewer_SizeChanged">
        <Grid Width="Auto">
            <Grid.RowDefinitions>
                <RowDefinition Height="38"></RowDefinition>
                <RowDefinition Height="*"></RowDefinition>
            </Grid.RowDefinitions>
            <StackPanel Height="38" Width="Auto" Background="#09527B">
                <Grid Margin="0,0,0,0">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition></ColumnDefinition>
                        <ColumnDefinition Width="70"></ColumnDefinition>
                    </Grid.ColumnDefinitions>
                </Grid>
            </StackPanel>
            <Grid Grid.Row="1">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="189"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Grid Grid.Column="0">
                    <StackPanel>
                        <Expander Name="test" Header="Admin" Foreground="White" Margin="0,10,0,0">
                            <StackPanel  Margin="20,0,0,0">
                                <Expander Header="Data" Foreground="White">
                                    <StackPanel>
                                        <TextBlock Text="Add/Edit UC1" Foreground="White" Margin="30,5,0,0" MouseDown="OpenUC1_MouseDown" MouseEnter="TextBlock_MouseEnter" MouseLeave="TextBlock_MouseLeave"/>
                                        <TextBlock Text="Add/Edit UC2" Name="tbxBuild" Foreground="White" Margin="30,5,0,0" MouseDown="OpenUC2_MouseDown" MouseEnter="TextBlock_MouseEnter" MouseLeave="TextBlock_MouseLeave"/>
                                    </StackPanel>
                                </Expander>                               
                    </StackPanel>
                </Grid>
                <StackPanel Grid.Column="1">
                    <Grid Name="pnlMain" Height ="Auto" VerticalAlignment="Top" HorizontalAlignment="Left">
                    </Grid>
                </StackPanel>
            </Grid>
        </Grid>
    </ScrollViewer>
</Window>

MainWindow.cs

private void OpenUC1_MouseDown(object sender, MouseButtonEventArgs e)
{
for (int i = 0; i < pnlMain.Children.Count; i++ )
            {
                pnlMain.Children.Remove(pnlMain.Children[i]);

            }
using (UC2 _uc2= new UC2())
            {
                pnlMain.Children.Add(_uc2);
            }
}

private void OpenUC2_MouseDown(object sender, MouseButtonEventArgs e)
{
for (int i = 0; i < pnlMain.Children.Count; i++ )
            {
                pnlMain.Children.Remove(pnlMain.Children[i]);

            }
using (UC1 _uc1= new UC1())
            {
                pnlMain.Children.Add(_uc1);
            }
}

我的问题是,当我从主面板中移除控件(UC1)时,该控件何时会被释放? 用户控件(UC1 和 UC2)都有相同的视图模型附加到其数据上下文。因此,我发现即使从面板中删除了已删除的用户控件(UC1)中的某些方法,也会调用这些方法。原因是,当创建 UC2 的新实例时,数据模型中会发生一些变化,这实际上调用了 UC1 中的依赖方法。 但如果 UC1 已被处置,这将不会发生。如何确保在创建 UC2 实例之前释放 UC1?

public UC1()
        {
            InitializeComponent();            
            this.DataContext = App.ViewModel.TestViewModel;
        }

 private void UC1_Unloaded(object sender, RoutedEventArgs e)
        {
            this.DataContext = null;
        }


public UC2()
        {
            InitializeComponent();            
            this.DataContext = App.ViewModel.TestViewModel;
        }

 private void UC2_Unloaded(object sender, RoutedEventArgs e)
        {
            this.DataContext = null;
        }

从面板中移除控件时,不会立即调用 unloaded 方法。

【问题讨论】:

  • 这个问题不清楚。 “Disposed”在 .NET/C# 的上下文中具有非常特殊的含义:“调用对象的 IDisposable.Dispose() 方法”。您似乎没有在此处处理IDisposable,因此使用“处置”一词是不正确且具有误导性的。一般来说,Unloaded 事件应该引发“当从加载元素的元素树中删除元素时”,正如 MSDN 所说。但是如果没有可靠地重现问题的a good, minimal, complete code example,很难说为什么它不适合你

标签: c# .net wpf xaml mvvm


【解决方案1】:

当我编写和测试代码以从窗口的可视化树中动态添加和删除UserControl 对象时,我发现Unloaded 事件正如预期的那样被引发。

在您自己的代码示例中,至少存在一个严重问题和两个不一致之处:

  1. 严重的问题是如何移除孩子。您的for 循环正在通过pnlMain 对象(Grid)的子对象的索引进行迭代。 但是删除任何子元素会使索引序列无效!也就是说,循环将首先删除索引为 0 的子元素;这会导致索引 1 处的子元素现在变为索引 0 处的子元素。但循环会在继续之前递增索引,然后将移除索引 1 处的子元素。该子元素最初位于索引 2 处。该代码会跳过所有其他子项(即最初位于奇数索引处的子项),将其中一半作为Grid 的子项附加。
  2. 不一致#1:我希望名称中带有短语“OpenUC1”的方法可以添加UC1 的实例。但是,您的OpenUC1_MouseDown() 方法似乎正在添加UC2 的实例(反之亦然OpenUC2_MouseDown())。至少,代码中应该有一个注释,解释为什么代码与给定方法名称的预期不同。
  3. 不一致#2:在添加UserControl 对象时,在调用Add() 周围有一个using 语句。首先,UserControl 本身并没有实现IDisposable,所以除非你的类型实现了那个接口,否则这个代码甚至是不合法的。其次,即使您的UserControl 子类确实 实现了该接口,对我来说,dispose 您刚刚创建的对象似乎不是一个好主意,并且您在可视化树中保留的内容(即通过将其添加到 Grid 的子项中)。

不幸的是,正如我在评论中提到的那样,如果没有 a good, minimal, complete code example 可靠地重现您的问题,就不可能说出为什么您的代码不会像人们希望和/或期望的那样运行。 可能上述任何一点(尤其是第 1 点)是您所看到的行为的原因,但我无法确定。

如果在解决了这些问题之后(或以某种方式确定它们不是问题......尽管如果你可以合法地这样做,我认为代码仍然存在缺陷,从某种意义上说它是糟糕的设计),你会发现你的问题仍然存在,请编辑您的问题,使其包含一个良好、最小完整的代码示例,可以可靠地重现问题。


同时,这里有一个简单的代码示例,它说明了当对象从可视树中移除时,Unloaded 事件按预期引发的基本行为。请注意,虽然从Grid 对象的Children 集合中删除所有子对象的正确方法是简单地调用Clear() 方法(例如pnlMain.Children.Clear()),但我已经包含了一个基于循环的显式方法的示例工作。

XAML:

UserControl1.xaml

<UserControl x:Class="TestSO33289488UserControlUnloaded.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             Unloaded="UserControl_Unloaded"
             d:DesignHeight="300" d:DesignWidth="300">
  <Grid>
    <TextBlock Text="UserControl" FontSize="36"/>
  </Grid>
</UserControl>

MainWindow.xaml

<Window x:Class="TestSO33289488UserControlUnloaded.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
  <StackPanel>
    <Button x:Name="button1" Content="Add UserControl"
            HorizontalAlignment="Left" Click="Button_Click"/>
    <Grid x:Name="grid1"/>
  </StackPanel>
</Window>

C#:

UserControl1.xaml.cs

using System.Windows;
using System.Windows.Controls;

namespace TestSO33289488UserControlUnloaded
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
        }

        private void UserControl_Unloaded(object sender, RoutedEventArgs e)
        {
            MessageBox.Show("UserControl.Unloaded was raised");
        }
    }
}

MainWindow.xaml.cs

using System.Windows;

namespace TestSO33289488UserControlUnloaded
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private bool _removeUserControl;

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (_removeUserControl)
            {
                //grid1.Children.Clear();

                // Calling Clear() is better, but if you really want to loop,
                // it is possible to do correctly. For example:
                while (grid1.Children.Count > 0)
                {
                    grid1.Children.RemoveAt(grid1.Children.Count - 1);
                }

                button1.Content = "Add UserControl";
            }
            else
            {
                grid1.Children.Add(new UserControl1());
                button1.Content = "Remove UserControl";
            }

            _removeUserControl = !_removeUserControl;
        }
    }
}

【讨论】:

    【解决方案2】:

    引用MSDN forum entry 关于加载/卸载事件:

    事件是异步引发的,因此可能会有一些延迟 在导致事件的动作和事件本身之间。这 事件被有效地放入列表中,并且任务被添加到 调度员的队列。当该任务运行时,它会引发 列表。

    所以答案是您无法准确预测这些事件何时会引发,并且您不应该期望在您从其父控件中删除控件后立即调用它们。

    如果没有看到完整的项目,很难给你一个合适的解决方案,但这里有一个快速而肮脏的解决方案:与其确保给定的用户控件的事件被及时触发,不如让我们检查 Parent 属性 UC1/UC2 对象在运行该方法之前。如果该属性为 null,则 UC1/UC2 对象已被删除,您不应执行该方法。

    但是让我指出这段代码的一些问题:

    • MouseDown 事件处理程序中的 using 块有什么意义?您创建一个用户控件对象,将其添加到面板,然后立即调用它的 Dispose 方法? (这就是 using 块在 C# 中的作用)
    • 您不需要 for 循环来从 Panel 控件(如 Grid)中删除所有子元素。你可以在一行中做到这一点。 pnlMain.Children.Clear();

    【讨论】:

    • “ViewModel 不应该在 MVVM 应用程序中存储对 View 对象的引用”——虽然我同意这种说法,但您能否详细说明一下具体是什么 i> 在 OP 提供的代码中,您在这方面与您有关吗?视图模型通过数据绑定隐式 引用视图对象是正常的,我的假设是在 OP 的情况下所发生的一切。 IE。更改视图模型中的属性值会调用假定丢弃的UserControl 对象中的某些方法。在我忽略的 OP 帖子中是否有来自 VM 的明确参考?
    • "原因是,当创建一个新的 UC2 实例时,数据模型发生了一些变化,实际上调用了 UC1 中的依赖方法"。我假设“调用 UC1 中的依赖方法”意味着直接调用该方法,方法是持有对 UserControl 对象的引用。但你是对的,我不应该在没有看到其余代码的情况下做出这样的假设,我会删除我的答案的那部分。
    猜你喜欢
    • 2021-11-26
    • 1970-01-01
    • 1970-01-01
    • 2015-06-13
    • 2011-07-03
    • 2011-07-11
    • 2014-09-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多