2017-08-21 4 views
1

J'essaye de retirer correctement un UIElement d'un InlineUIContainer afin de l'employer dans un autre panneau mais le programme continue à s'écraser avec ce message "Specified Visual est déjà un enfant d'un autre visuel ou la racine d'un CompositionTarget. ".Comment se détacher de l'arborescence Visual dans WPF

J'ai créé une petite application pour illustrer ma douleur. Dans ce programme, une fois que Randy le bouton est tué \ supprimé par sa petite amie, il ne se détache pas encore de son parent, que j'ai découvert était UIElementIsland. Et puis toute tentative d'ajouter Randy comme l'enfant de toute autre chose bloque l'application (Le bouton Apocalypse prouve mon point :)). Vous pouvez cliquer pour vérifier les parents de Randy avant \ après la suppression de Randy pour remarquer qu'il est constamment sous UIElementIsland comme un enfant, s'il est détaché tout le problème \ apocalypse devrait être évité.

C'est une application amusante donc copiez et compilez même si c'est juste pour le plaisir! Toute aide \ idées serait appréciée!

C# Partie:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Windows; 
using System.Windows.Controls; 
using System.Windows.Data; 
using System.Windows.Documents; 
using System.Windows.Input; 
using System.Windows.Media; 
using System.Windows.Media.Imaging; 
using System.Windows.Navigation; 
using System.Windows.Shapes; 
using System.Windows.Threading; 

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

     int t = 0; 

     static string[] info = new string[] { "Okay, Lets have a look...", "Checking." 
      , "Checking..", "Checking...", "Seen it!" }; 

     /// <summary> 
     /// Makes the App fancy :) 
     /// </summary> 
     /// <param name="sender"></param> 
     /// <param name="e"></param> 
     void timer_Tick(object sender, EventArgs e) 
     { 
      display.Text = info[t]; 

      if (t == 0) 
       timer.Interval = new TimeSpan(0, 0, 0, 0, 300); 

      t++; 
      if (t >= 4) 
      { 
       t = 0; 
       timer.Stop(); 
       display.Text = GetRandysParent(); 
      } 
     } 

     private void deleteRandy_Click(object sender, RoutedEventArgs e) 
     { 
      // This might be the bug. 
      // Maybe there's a better way to do this. 
      // If there was a VisualTreeHelper.Remove(). 
      randy_container.Child = null; 

      display.Text = "Haha! I just killed Randy!!! He'll never get the chance" 
       + "\n to hurt another woman again!"; 
      display.Background = Brushes.Violet; 
      end.Visibility = System.Windows.Visibility.Visible; 
     } 

     DispatcherTimer timer = null; 

     /// <summary> 
     /// Check if Randy is Still attached to UIElementIsland 
     /// </summary> 
     /// <returns></returns> 
     private string GetRandysParent() 
     { 
      // Check the visual tree to see if randy is removed properly 
      DependencyObject dp = VisualTreeHelper.GetParent(randy); 
      string text = string.Empty; 
      if (dp != null) 
      { 
       display.Background = Brushes.LightGreen; 
       text = "Randy's Dad is Mr " + dp.ToString(); 
      } 

      else 
      { 
       // This should be what you'll get when the code works properly 
       display.Background = Brushes.Red; 
       text = "Weird...Randy doesn't seem to have a dad..."; 
      } 
      return text; 
     } 

     private void findParents_Click(object sender, RoutedEventArgs e) 
     { 
      display.Background = Brushes.Yellow; 

      // Creates a timer to display some fancy stuff 
      // and then Randy's. 
      // Just to prove to you that this button actually works. 
      timer = new DispatcherTimer(); 
      timer.Start(); 
      timer.Tick += timer_Tick; 
      timer.Interval = new TimeSpan(0, 0, 0, 0, 700); 
     } 

     private void randy_Click(object sender, RoutedEventArgs e) 
     { 
      // Get Randy to introduce himself 
      display.Text = "Hi, I'm Randy!!!"; 
      display.Background = Brushes.Orange; 
     } 

     private void end_Click(object sender, RoutedEventArgs e) 
     { 
      // If randy is removed properly, this would not crash the application. 
      StackPanel s = new StackPanel(); 
      s.Children.Add(randy); 
      // CRASH!!! 
     } 
    } 
} 

Le XAML:

<Window x:Class="DetachingfromUIElementIsland.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"> 
    <FlowDocument IsEnabled="True" x:Name="document"> 
     <Paragraph> 
      <InlineUIContainer x:Name="randy_container"> 
       <!--Meet Randy--> 
       <Button Name="randy" Content="I am a Randy, the button" Click="randy_Click" ToolTip="Meet Randy"/> 
      </InlineUIContainer> 
      <LineBreak/> 
      <LineBreak/> 
      <InlineUIContainer x:Name="container2"> 
       <!--Meet Randy's Ex Girlfriend--> 
      <Button Name="deleteRandy" Content="Randy dumped me for another girl :(, click me to delete him" Click="deleteRandy_Click" ToolTip="Meet Randy's Ex Girlfriend"/> 
      </InlineUIContainer> 
      <LineBreak/> 
      <LineBreak/> 
      <InlineUIContainer x:Name="container3"> 
       <!--He can help you find Randy's Parents--> 
      <Button Name="findParents" Content="Click me to find randy's parents" Click="findParents_Click" ToolTip="He can help you find Randy's Parents"/> 
      </InlineUIContainer> 
      <LineBreak/> 
      <LineBreak/> 
      <InlineUIContainer x:Name="Apocalypse"> 
       <!--End the world, Crash the application--> 
       <Button x:Name="end" Content="Avenge Randy's Death" Click="end_Click" ToolTip="End the world, Crash the application" Visibility="Hidden"/> 
      </InlineUIContainer> 
     </Paragraph> 
     <Paragraph> 
      <InlineUIContainer> 
       <TextBlock x:Name="display" Foreground="Black"/> 
      </InlineUIContainer> 
     </Paragraph> 
    </FlowDocument> 
</Window> 

Le code tout était censé être plus courte que cela, mais je pimentée vers le haut pour en faire un peu fun. J'espère avoir illuminé un peu la journée de quelqu'un. Mais encore, aidez-moi :).

Réponse: Derive de Randy InlineUIContainer comme suit:

public class DerivedInlineUIContainer : InlineUIContainer 
    { 
     public DerivedInlineUIContainer() 
     { 

     } 

     public void RemoveFromLogicalTree(FrameworkElement f) 
     { 
      this.RemoveLogicalChild(f); 
     } 
    } 

Maintenant, vous pourriez tuer Randy correctement cette fois-ci, et l'ajouter au UIElement ciel (Le StackPanel):

randy_container.RemoveFromLogicalTree(randy); 
    IDisposable disp = VisualTreeHelper.GetParent(randy) as IDisposable; 
    if (disp != null) 
     disp.Dispose(); 

    // Poor Randy is going to heaven... 
    StackPanel heaven = new StackPanel(); 
    heaven.add(randy); 

Merci tout le monde.

+0

"épicé pour le rendre un peu amusant" est auto-défait ici. Publiez un [MCVE] (https://stackoverflow.com/help/mcve) à la place. – Clemens

+0

Je pense que vous devez également supprimer le InlineUiContainer de son parent (c'est-à-dire le Paragraphe). Je ne sais pas pourquoi, mais j'ai une méthode d'extension dans mon code qui fait exactement cela parce que j'ai eu les mêmes problèmes il y a quelques mois. – Lennart

+0

Pris note de cela. Je ferai amende honorable la prochaine fois. Merci @Clemens –

Répondre

1

Suppression du parent visuel ne semble pas aider:

private void end_Click(object sender, RoutedEventArgs e) 
{ 
    IDisposable disp = VisualTreeHelper.GetParent(randy) as IDisposable; 
    if (disp != null) 
     disp.Dispose(); 

    DependencyObject parent = VisualTreeHelper.GetParent(randy); 
    if (parent == null) 
     MessageBox.Show("No parent"); 

    // If randy is removed properly, this would not crash the application. 
    StackPanel s = new StackPanel(); 
    s.Children.Add(randy); 
} 

soit vous pouvez donc créer une nouvelle Button:

public MainWindow() 
{ 
    InitializeComponent(); 
    randy_container.Child = CreateRandyButton(); 
} 

private void end_Click(object sender, RoutedEventArgs e) 
{ 
    StackPanel s = new StackPanel(); 
    s.Children.Add(CreateRandyButton()); 
} 

private Button CreateRandyButton() 
{ 
    Button button = new Button { Name = "randy", Content = "I am a Randy, the button", ToolTip = "Meet Randy" }; 
    button.Click += randy_Click; 
    return button; 
} 

... ou tout simplement le cacher comme suggéré par @Sinatr .

+0

Oui, il plante encore, mais le message a changé pour "L'élément spécifié est déjà l'enfant logique d'un autre élément." Déconnectez-le d'abord. ". Je vais voir comment je peux résoudre cela –

+0

Il ne devrait pas planter lorsque vous faites: s.Children.Add (CreateRandyButton()); – mm8

+1

Salut! Bon travail!!! J'ai dérivé de 'InlineUIContainer' et appelé' RemoveLogicalChild() '. Avec votre suggestion sur 'Dispose()'. Et ça a marché. Merci beaucoup! –

0

C'est marrant, mais aussi très bruyant. Vous obtiendrez une réponse beaucoup plus rapide si votre démo est courte.

Au lieu de supprimer/ajouter visuelle, vous pouvez simplement cacher/montrer:

void deleteRandy_Click(object sender, RoutedEventArgs e) => 
    randy.Visibility = Visibility.Hidden; 

void end_Click(object sender, RoutedEventArgs e) => 
    randy.Visibility = Visibility.Visible; 

De cette façon, vous ne jouez pas avec l'arbre visuel de manière irrécupérable. Vous pouvez utiliser les modèles de données MVVM + ou les ressources x:Shared=False si vous voulez vraiment supprimer l'élément de l'interface utilisateur, puis ajouter nouveau.

+0

Ça sonne bien, mais c'est juste une démo. Je veux utiliser la logique dans une riche zone de texte. –