It is necessary to place through Binding a collection of pictures on Canvas, and so that each picture can be moved with the mouse.

View (XAML)

<Grid Background="DarkViolet"> <Grid.RowDefinitions> <RowDefinition Height="*" /> </Grid.RowDefinitions> <ItemsControl x:Name="Ccc" Grid.Row="0" ItemsSource="{Binding Path=Images}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas x:Name="fff" Width="800" Height="600" Margin="15" HorizontalAlignment="Left" VerticalAlignment="Top" Background="Chartreuse"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <Thumb Canvas.Left= "10" Canvas.Top="10" DragDelta="Thumb_DragDelta"> <Thumb.Template> <ControlTemplate> <Image Width="80" Height="80" Source="{Binding}" Stretch="UniformToFill" /> </ControlTemplate> </Thumb.Template> </Thumb> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </Grid> 

CodeBehind - an event for moving a picture inside a Canvas (catel framework is applied, but properties may be normal)

 private void Thumb_DragDelta(object sender, DragDeltaEventArgs e) { var thumb = e.Source as FrameworkElement; var horizontalChange = Canvas.GetLeft(thumb) + e.HorizontalChange; var verticalChange = Canvas.GetTop(thumb) + e.VerticalChange; var maxHorizontalPoint = Ccc.ActualWidth - thumb.ActualWidth; var maxVerticalPoint = Ccc.ActualHeight - thumb.ActualHeight; if (horizontalChange < 0) { Canvas.SetLeft(thumb, 0); } else if (horizontalChange > maxHorizontalPoint) { Canvas.SetLeft(thumb, maxHorizontalPoint); } else { Canvas.SetLeft(thumb, horizontalChange); } if (verticalChange < 0) { Canvas.SetTop(thumb, 0); } else if (verticalChange > maxVerticalPoint) { Canvas.SetTop(thumb, maxVerticalPoint); } else { Canvas.SetTop(thumb, verticalChange); } } 

ViewModel - contains the BitmapImage collection

 public class MainWindowViewModel : ViewModelBase { public MainWindowViewModel() { var circleUri = new Uri(String.Format(@"F:\Pictures\circle.png")); var rectangleUri = new Uri(String.Format(@"F:\Pictures\rectangle.png")); Image = new BitmapImage(circleUri); Images = new ObservableCollection<BitmapImage> { new BitmapImage(circleUri), new BitmapImage(rectangleUri) }; } public BitmapImage Image { get { return GetValue<BitmapImage>(ImageProperty); } set { SetValue(ImageProperty, value); } } public static readonly PropertyData ImageProperty = RegisterProperty("Image", typeof(BitmapImage)); public ObservableCollection<BitmapImage> Images { get { return GetValue<ObservableCollection<BitmapImage>>(ImagesProperty); } set { SetValue(ImagesProperty, value); } } public static readonly PropertyData ImagesProperty = RegisterProperty("Images", typeof(ObservableCollection<BitmapImage>)); } 

Pictures are added to the Canvas but do not mix, because Thumb does not see attached properties from Canvas. Canvas.Left and Canvas.Top. and any movements relative to the Canvas do not work accordingly.

 <Thumb Canvas.Left= "10" Canvas.Top="10"/> 

A single object in the Canvas correctly handles the move.

 <Canvas x:Name="Ccc" Grid.Row="0" Width="800" Height="600" Margin="15" HorizontalAlignment="Left" VerticalAlignment="Top" Background="Chartreuse"> <Thumb Canvas.Left="10" Canvas.Top="10" Canvas.ZIndex="99" DragDelta="Thumb_DragDelta"> <Thumb.Template> <ControlTemplate> <Image Width="80" Height="80" Source="{Binding Path=Image}" Stretch="UniformToFill" /> </ControlTemplate> </Thumb.Template> </Thumb> </Canvas> 

Update

Replaced by

  <ItemsControl x:Name="Ccc" Grid.Row="0" ItemsSource="{Binding Path=Images}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas Width="800" Height="600" Margin="15" HorizontalAlignment="Left" VerticalAlignment="Top" Background="Chartreuse"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemContainerStyle> <Style TargetType="ContentPresenter"> <Setter Property="Canvas.Left" Value="10"/> <Setter Property="Canvas.Top" Value="10"/> <Setter Property="ContentTemplate"> <Setter.Value> <DataTemplate> <Thumb DragDelta="Thumb_DragDelta" > <Thumb.Template> <ControlTemplate> <Image Width="80" Height="80" Source="{Binding}" Stretch="UniformToFill" /> </ControlTemplate> </Thumb.Template> </Thumb> </DataTemplate> </Setter.Value> </Setter> </Style> </ItemsControl.ItemContainerStyle> </ItemsControl> 

But also did not help.

  • Look through Snoop - I think that inside Canvas ' it’s not Item that is located directly, but ItemContainer , and you need to set coordinates not in ItemTemplate , but in ItemContainerTemplate . It may be easier to abandon ItemsControl , self-subscribe to the ObservableCollection change, and put Thumb in the code-behind directly on Canvas . - VladD
  • add to CodeBehind via fff.Children.Add (new UIElement ()); where fff is the name in XAML. but I didn't want to do that, I wanted to add a collection from the ViewModel. - Aldmi
  • This is yes, then try through the ItemContainerTemplate . Undoubtedly, the addition to the code-behind is not so elegant, so if it can be avoided, it is worth it. - VladD
  • I downloaded Snoop, I found 2 ContentPresenter in it in the Canvas tree and in each on Thumb. I did not find the ItemTemplate or ItemContainerTemplate property in the right window ((. That is, you wanted to replace <ItemsControl.ItemTemplate> with <ItemsControl.ItemContainerTemplate> but this does not work. - Aldmi
  • No, no, you need both. ItemTemplate manages how the list item is displayed. And ItemContainerTemplate - by what it is packed. For example, the blue background color of an element with a focus is done using the ItemContainer. - VladD

4 answers 4

Thank you very much, everything turned out, made a wrapper.

Driver class:

  public class Driver : ModelBase { public BitmapImage Image { get { return GetValue<BitmapImage>(ImageProperty); } set { SetValue(ImageProperty, value); } } public static readonly PropertyData ImageProperty = RegisterProperty("Image", typeof(BitmapImage)); public double Xpos { get { return GetValue<double>(XposProperty); } set { SetValue(XposProperty, value); } } public static readonly PropertyData XposProperty = RegisterProperty("Xpos", typeof(double)); public double Ypos { get { return GetValue<double>(YposProperty); } set { SetValue(YposProperty, value); } } public static readonly PropertyData YposProperty = RegisterProperty("Ypos", typeof(double)); public Driver(string pathImage, double xPos, double yPos) { Image= new BitmapImage(new Uri(pathImage)); Xpos= xPos; Ypos = yPos; } } 

To shuffle the selected image, you need to find Drivers[i] in the event handler and change Xpos , Ypos . Now I always change at the first picture.

  private void Thumb_DragDelta(object sender, DragDeltaEventArgs e) { var thumb = e.Source as FrameworkElement; var datacont = (MainWindowViewModel)DataContext; var horizontalChange = datacont.Drivers[0].Xpos + e.HorizontalChange; var verticalChange = datacont.Drivers[0].Ypos + e.VerticalChange; } 
  • for example, having received Image from the publisher Thumb it would be possible to find the necessary driver by Uri - user185942
  • Everything is done you just need to get the Thumb DataContext itself. var driverEh = (Driver) thumb.DataContext; - user185942

Try this:

 <ItemsControl ItemsSource="{Binding Items}"> <ItemsControl.ItemContainerStyle> <Style> <Setter Property="Canvas.Left" Value="{Binding X}"/> <Setter Property="Canvas.Top" Value="{Binding Y}"/> </Style> </ItemsControl.ItemContainerStyle> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas x:Name="fff" Width="800" Height="600" Margin="15" HorizontalAlignment="Left" VerticalAlignment="Top" Background="Chartreuse"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <!-- оставьте как у вас --> </ItemsControl.ItemTemplate> </ItemsControl> 

You still have to put DependencyProperty (not ordinary properties!) X and Y in the VM.

Well, in Thumb_DragDelta also set the properties of the VM, not Canvas.Left / Top .

  • Yeah, I started doing this myself. There is a stupid question). How to set VM properties in codeBehind? - Aldmi
  • @Aldmi: Well, the VM itself can be obtained, for example, from DataContext . Just do not forget to cast in the desired type. - VladD
  • Thank you very much for your help, I started modifying the application to make the selection of pictures on the canvas, etc. and ran into the problem of forwarding Events to the VM command. The code and the problem itself are described in my answer to the topic. - Aldmi
  <ItemsControl Grid.Row="0" Grid.Column="1" ItemsSource="{Binding Path=Drivers}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas Width="800" Height="600" Margin="5,15,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Background="Chartreuse"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemContainerStyle> <Style TargetType="ContentPresenter"> <Setter Property="Canvas.Left" Value="{Binding Path=Xpos}"/> <Setter Property="Canvas.Top" Value="{Binding Path=Ypos}"/> <Setter Property="ContentTemplate"> <Setter.Value> <DataTemplate> <Button Height="30" Width="30" > <i:Interaction.Triggers > <i:EventTrigger EventName="MouseDoubleClick" > <catel:EventToCommand Command="{Binding Path=TestCommand, RelativeSource= {RelativeSource Mode=TemplatedParent}}"/> </i:EventTrigger > </i:Interaction.Triggers> </Button> </DataTemplate> </Setter.Value> </Setter> </Style> </ItemsControl.ItemContainerStyle> </ItemsControl> 

for debugging, I replaced the thumb with a button and forwarded the DoubleClick event. In the DataContext window installed on my VM and there are no problems with this, the command forwarding mechanism is also working (debugged on a simple button). But TestCommand does not work while inside the ContentTemplate, i.e. does not see the VM. How to set the RelativeSource correctly for such a case?

Even the zabindennaya team itself does not work directly in the DataTemplate.

  <DataTemplate> <Button Height="30" Width="30" Command="{Binding Path=TestCommand}"/> </DataTemplate> 
  • This is another problem. It is worth describing a separate issue. - VladD

I solved all the problems. for Binding in the template you need to use:

 Command="{Binding Path=DataContext.TestCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"