I got a list with Images and their Left/Top locations, that I add to a Canvas. However, I want to be able to add the same Images (same source) to the Canvas, without any problems.
When I just use the following code:
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");
when that same Image(-source) is already present on the Canvas (with a different Left/Top location and Name), I get the following exception:
ArgumentException: Specified Visual is already a child of another Visual or the root of a CompositionTarget.
So I know I'm not allowed to add the same UIElement (in my case Image) to the same Canvas.
I modified my code to:
// 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");
This resolved the error, but now I have a new problem.. I do get two Images on the Canvas, but the Image already present got it's location (Left and Top) reset to 0,0 (the same as the newly added Image), and when I made a Console.Write-test, I also noticed the Name of both Images on the Canvas are the same now.
What am I doing wrong with the Cloning which makes the first Image's Name, Left, Top (and probably other things) the same as the second Image (the "Clone")?
Thanks in advance for the responses.
EDIT: After the suggestion of Clemens, I changed my xaml from:
<Canvas Name="MyCanvas" Background="LimeGreen"/>
To:
<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>
The ImagesList now also contains BitmapImage ImagesSource instead of Image Image.
But I wasn't able to test it yet, because it gives a bunch of errors. I use the Canvas as a parameter in the Constructor of the ViewModel for instance, but don't know how to access it now that it's in a ItemsControl. And I also get some errors within my MouseEvent functions, where I use the img.Parent to get the Canvas (which isn't working anymore with the code above) AND canvas.Children to get all the images including their ZIndex (which also isn't working anymore).
EDIT2 / SOLUTION:
After undoing the Edit above because I was getting to much errors because of my other crappy code parts, except for the ImageSource I saved in the list instead of the Image itself, it turns out it works now.
You would typically do this with an ItemsControl
that has a Canvas as ItemsPanel
and an ItemContainerStyle
that binds the Canvas.Left
and Canvas.Top
properties to appropriate properties in your data item class.
If this is your data item class:
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; }
}
the XAML of the ItemsControl would look like shown below. Note that its ItemsSource
property is bound to the ImageItems
property in your ViewModel class.
<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>
Somewhere in MainWindow initialization code:
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
});