【问题标题】:C# WPF ArgumentException: Specified Visual is already a child of another Visual or the root of a CompositionTargetC# WPF ArgumentException:指定的 Visual 已经是另一个 Visual 的子对象或 CompositionTarget 的根
【发布时间】:2014-03-19 23:11:36
【问题描述】:

我得到了一个包含图像及其左侧/顶部位置的列表,我将其添加到画布中。但是,我希望能够将相同的图像(相同的来源)添加到画布,没有任何问题。

当我只使用以下代码时:

Image img = ImagesList[i].Image; // ImagesList is a list of MyClass (containing Image Image; double Left; double Top)
img.Name = "img" + i; // where i is the nr in the list
Canvas.SetLeft(img, ImagesList[i].Left); // Left default = 0
Canvas.SetTop(img, ImagesList[i].Top); // Top default = 0
MyCanvas.Children.Add(img);
OnPropertyChanged("MyCanvas");

当相同的 Image(-source) 已经出现在画布上时(具有不同的左/上位置和名称),我得到以下异常:

ArgumentException: Specified Visual is already a child of another Visual or the root of a CompositionTarget.

所以我知道我不允许将相同的 UIElement(在我的情况下为 Image)添加到相同的 Canvas。

我将代码修改为:

// If the Image already exists on the Canvas, we need to make a clone of the image
if (MyCanvas.Children.Contains(img)) {
    Image cloneImg = new Image();
    cloneImg.Source = img.Source;
    cloneImg.Name = img.Name;
    Canvas.SetLeft(cloneImg, Left);
    Canvas.SetTop(cloneImg, Top);
    MyCanvas.Children.Add(cloneImg);
}
else
    MyCanvas.Children.Add(img);
OnPropertyChanged("MyCanvas");

这解决了错误,但现在我遇到了一个新问题。我确实在画布上获得了两个图像,但是已经存在的图像将其位置(左侧和顶部)重置为 0,0(与新的相同)添加了图像),当我进行 Console.Write-test 时,我还注意到画布上的两个图像的名称现在是相同的。

克隆使第一个图像的名称、左侧、顶部(可能还有其他内容)与第二个图像(“克隆”)相同的克隆有什么问题?

提前感谢您的回复。


编辑:在 Clemens 的建议下,我将我的 xaml 更改为:

<Canvas Name="MyCanvas" Background="LimeGreen"/>

收件人:

<ItemsControl ItemsSource="{Binding MyField.ImagesList}"> <!-- MyField is the class where I have my list -->
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas Name="FieldCanvas" Background="LimeGreen" IsItemsHost="True"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="ContentPresenter">
                <Setter Property="Canvas.Left" Value="{Binding Left}"/>
                <Setter Property="Canvas.Top" Value="{Binding Top}"/>
            </Style>
        </ItemsControl.ItemContainerStyle>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Image Source="{Binding ImageSource}" AllowDrop="True" PreviewMouseLeftButtonDown="Image_PreviewMouseLeftButtonDown" PreviewMouseMove="Image_PreviewMouseMove" PreviewMouseLeftButtonUp="Image_PreviewMouseLeftButtonUp"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

ImagesList 现在还包含 BitmapImage ImagesSource 而不是 Image Image。

但我还不能测试它,因为它给出了一堆错误。例如,我在 ViewModel 的构造函数中使用 Canvas 作为参数,但现在不知道如何访问它,因为它位于 ItemsControl 中。 而且我在 MouseEvent 函数中也遇到了一些错误,我使用 img.Parent 来获取 Canvas (不再使用上面的代码)和 canvas.Children 来获取所有图像,包括它们的 ZIndex (这也是'不再工作了)。


EDIT2 / 解决方案:

撤消上面的编辑后,因为我的其他糟糕的代码部分导致了很多错误,除了我保存在列表中的 ImageSource 而不是 Image 本身,结果证明它现在可以工作了。

【问题讨论】:

  • 你应该以不同的方式做整个事情。使用带有 Canvas 作为 ItemsPanel 和 ItemContainerStyle 的 ItemsControl,绑定每个图像项的 Left 和 Top 属性。
  • 我注意到您在发布的代码中使用了cloneImagecloneImg。是错字吗?
  • @Vanlalhriata 是的,这是一个错字。固定。
  • @Clemens 你能给出一个代码示例吗?我对 C# WPF 有点陌生。根据您的建议,我对迄今为止的尝试进行了编辑。

标签: c# wpf canvas clone argumentexception


【解决方案1】:

您通常会使用具有 ItemsPanel 的 Canvas 的 ItemsControl 和将 Canvas.LeftCanvas.Top 属性绑定到数据项类中的适当属性的 ItemContainerStyle 来执行此操作。

如果这是您的数据项类:

public class ImageItem
{
    public string Source { get; set; }
    public double Left { get; set; }
    public double Top { get; set; }
}

public class ViewModel
{
    public ViewModel()
    {
        ImageItems = new ObservableCollection<ImageItem>();
    }

    public ObservableCollection<ImageItem> ImageItems { get; private set; }
}

ItemsControl 的 XAML 如下所示。请注意,它的 ItemsSource 属性绑定到 ViewModel 类中的 ImageItems 属性。

<ItemsControl ItemsSource="{Binding ImageItems}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas IsItemsHost="True"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="ContentPresenter">
                <Setter Property="Canvas.Left" Value="{Binding Path=Left}"/>
                <Setter Property="Canvas.Top" Value="{Binding Path=Top}"/>
            </Style>
        </ItemsControl.ItemContainerStyle>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Image Source="{Binding Path=Source}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

MainWindow 初始化代码中的某处:

var vm = new ViewModel();
DataContext = vm;

vm.ImageItems.Add(
    new ImageItem
    {
        Source = @"C:\Users\Public\Pictures\Sample Pictures\Koala.jpg",
        Left = 100,
        Top = 50
    });
vm.ImageItems.Add(
    new ImageItem
    {
        Source = @"C:\Users\Public\Pictures\Sample Pictures\Desert.jpg",
        Left = 200,
        Top = 100
    });

【讨论】:

  • 如果有人需要像我一样做一个类似的项目,从一开始就使用上面的 Clemens 代码,而不是我的。
【解决方案2】:

在我用 Clemens 的建议恢复所有尝试后,同时在我的列表中保留 BitmapImage ImageSource 而不是 Image Image 的更改,它现在可以工作了。

我知道我的代码不是最好的,而 Clemens 的代码确实要好得多。但是由于我已经有很多代码,并且(在我看来)将其更改为 Clemens 代码而与我的其他代码保持相同的工作方式太麻烦了,所以除了更改 BitmapImage ImageSource 之外,我将其恢复回来,事实证明它现在可以工作了。

如果我做过类似的项目,我会从 Clemens 开始使用您的代码,因为它在 WPF 编程和代码架构方面要好得多。非常感谢您的回复。

我的问题的解决方案:我没有将图像本身保存在列表中并对其进行“克隆”,而是将我的 ImageSource 保存在列表中。所以我总是制作一个新的 Image() 来更改 Source。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-23
    • 1970-01-01
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    相关资源
    最近更新 更多