1

Необходимо разместить через Binding коллекцию картинок на Canvas, и чтобы каждую картинку можно было перемещать мышкой.

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 - событие для перемещения картинки внутри Canvas (применен фреймворк catel, но свойства могут быть обычными)

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 - содержит коллекцию BitmapImage

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>));

}

Картинки добавляются на Canvas но не перемешаются, т.к. Thumb не видит прикрепленные свойства от Canvas. Canvas.Left и Canvas.Top. и любые перемещения относительно Canvas соответственно не получаются.

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

Одиночный объект в Canvas корректно отрабатывает перемещение.

<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>

Обновление

Заменил на

    <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>

Но тоже не помогло.

Aldmi
  • 1,925
  • Посмотрите через Snoop — я думаю, что внутри Canvas'а расположен не непосредственно Item, а ItemContainer, и устанавливать координаты нужно не в ItemTemplate, а в ItemContainerTemplate. Возможно, проще будет отказаться от ItemsControl, самостоятельно подписываться на изменение ObservableCollection, и класть Thumb'ы в code-behind прямо на Canvas. – VladD Jul 21 '15 at 08:37
  • добавлять в CodeBehind можно через fff.Children.Add(new UIElement()); где fff - имя в XAML. но не хотелось так делать, хотелось прибиндить коллекцию из ViewModel. – Aldmi Jul 21 '15 at 09:04
  • Это да, тогда попробуйте через ItemContainerTemplate. Несомненно, добавление в code-behind не настолько изящно, так что если его можно избежать, так стоит и делать. – VladD Jul 21 '15 at 09:12
  • Скачал Snoop, нашел в дереве Canvas в нем 2-а ContentPresenter и в каждом по Thumb. Свойство ItemTemplate или ItemContainerTemplate я не нашел в правом окне((. То есть вы хотели <ItemsControl.ItemTemplate> заменить на <ItemsControl.ItemContainerTemplate> но так не получается. – Aldmi Jul 21 '15 at 09:27
  • Нет-нет, надо и то, и другое. ItemTemplate заведует тем, как элемент списка отображается. А ItemContainerTemplate — тем, во что он при этом упаковывается. Например, синий цвет фона элемента с фокусом делается именно при помощи ItemContainer'а. – VladD Jul 21 '15 at 09:31
  • Если вы посмотрите в Snoop'е, вы увидите, что Thumb не кладётся прямо в Canvas, именно поэтому Canvas.SetLeft не работает. – VladD Jul 21 '15 at 09:31
  • есть <ItemsControl.ItemContainerStyle> но ItemContainerTemplate не нашел. Thumb кладется в ContentPresenter . – Aldmi Jul 21 '15 at 09:35

4 Answers4

1

Спасибо огромное, все получилось, сделал обертку.

Класс Driver:

    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;
    }
}

Чтобы перемешать выделенную картинку, нужно в обработчике события находить Drivers[i] и менять Xpos, Ypos. Сейчас я меняю всегда у первой картинки.

    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;
    }
Vladimir Glinskikh
  • 1,735
  • 9
  • 19
  • 28
user185942
  • 11
  • 1
  • например получив Image из издателя Thumb можно было бы по Uri найти нужный драйвер – user185942 Jul 21 '15 at 15:00
  • все сделал нужно просто получать DataContext самого Thumb. var driverEh = (Driver)thumb.DataContext; – user185942 Jul 21 '15 at 15:41
0

Попробуйте так:

<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>

Вам придётся ещё положить DependencyProperty (не обыкновенные свойства!) X и Y в VM.

Ну и в Thumb_DragDelta тоже устанавливать свойства VM, а не Canvas.Left/Top.

VladD
  • 206,799
  • ага по этому пути начал уже сам делать. Есть тупой вопрос). как устанавливать свойства VM в codeBehind? – Aldmi Jul 21 '15 at 10:15
  • @Aldmi: Ну, саму VM можно получить, например, из DataContext'а. Только не забудьте закастить в нужный тип. – VladD Jul 21 '15 at 10:38
  • Спасибо огромное за помошь, начал дорабатывать приложение делать выделение картинок на холсте и т.д. и столкнулся с проблемой проброса События в команду VM. код и сама проблема описаны в моем ответе к теме. – Aldmi Jul 23 '15 at 07:28
0
    <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>

для отладки я заменил thumb на button и пробрасываю событие DoubleClick. В окне DataContext установлено на мою VM и проблем с этим нету, механизм проброса команды тоже рабочий (отлаживал на простой кнопке). Но TestCommand не срабатывает находясь внутри ContentTemplate, т.е. не видит VM. Как правильно задать RelativeSource вот для такого случая?

Не срабатывает даже сама команда забинденная напрямую в DataTemplate.

          <DataTemplate>
            <Button Height="30" Width="30" Command="{Binding Path=TestCommand}"/>
          </DataTemplate>
Aldmi
  • 1,925
  • Это уже другая проблема. Её стоит описать отдельным вопросом. – VladD Jul 31 '15 at 15:21
0

все проблемы решил. для Биндинга в шаблоне нужно использовать:

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