首页 文章

删除wpf中对用户控件的引用

提问于
浏览
0

我有一个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;
        }

从面板中删除控件时,不会立即调用卸载的方法 .

2 回答

  • 0

    当我编写并测试代码以从窗口的可视化树中动态添加和删除 UserControl 对象时,我发现 Unloaded 事件是按预期引发的 .

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

    • 严重的问题是如何移除儿童 . 您的 for 循环通过索引遍历 pnlMain 对象的子项( Grid ) . But removing any child invalidates the sequence of indexes! 也就是说,循环将首先删除索引0处的子节点;这会导致索引1处的子节点现在成为索引0处的子节点 . 但循环在继续之前递增索引,然后将删除索引1处的子节点 . 此子节点最初位于索引 2 处 . 代码会跳过其他所有子代(即最初在奇数编号索引处的代码),其中一半作为 Grid 的子代附加 .

    • Incongruity#1:我希望在名称中使用短语"OpenUC1"的方法添加 UC1 的实例 . 但是,您的 OpenUC1_MouseDown() 方法似乎正在添加 UC2 的实例(反之亦然 OpenUC2_MouseDown() ) . 至少,代码中应该有一个注释,解释为什么代码与给定方法名称的预期不同 .

    • Incongruity#2:在添加 UserControl 对象时,对 Add() 的调用有一个 using 语句 . 首先, UserControl 本身没有实现 IDisposable ,所以除非您的类型已经实现了该接口,否则该代码甚至不合法 . 其次,即使你的 UserControl 子类确实实现了那个接口,对我来说,处理你刚刚创建的对象以及你在可视化树中保留的对象(例如通过将它添加到 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;
            }
        }
    }
    
  • 0

    关于 Loaded/Unloaded 事件引用MSDN forum entry

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

    所以答案是你无法预测这些事件的确切时间会被提出,你不应该期望在你从父母那里删除一个控件后立即调用它们 .

    它是一个快速而肮脏的解决方案:而不是确保给定的用户控制' events are fired in time let'在运行方法之前检查UC1 / UC2对象的 Parent property . 如果属性为null,则删除UC1 / UC2对象,不应执行该方法 .

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

    • 什么's the point of the using block in the MouseDown event handlers? You create a user control object, add it to the panel and then immediately after that you call the Dispose method on it? (that'什么是使用块在C#中做什么

    • 您不需要for循环来从Panel控件中删除所有子元素,如Grid . 你可以在一行中做到这一点 . pnlMain.Children.Clear();

相关问题