如何在画布上移动/调整大小/旋转ContentControl?

丹尼尔·多罗申科(Daniil Doroshenko)

我试图用WPF创建图设计器并使用MVVM模式,我从本指南中获取信息和一些技巧:https : //www.codeproject.com/Articles/22952/WPF-Diagram-Designer-Part-1

一会儿,我的项目看起来像:

WPF:在运行时具有可拖动控件的水合画布

当然,我也遇到了与上述作者类似的问题:当绘制我的时ContentControl,它使用随机坐标正确绘制,但是当我尝试移动它时,它就不会移动!当我调试类时MoveThumb,我看到我的类ContentControl还没有Parent但在我看来,它应该有Canvas一个Parent我知道我应该覆盖某些系统/基本方法,但是我不明白应该覆盖什么以及如何覆盖它。也许有人有主意?

现在,我尝试描述我的实现,首先创建BaseShapeViewModel

abstract public class BaseShapeViewModel : BaseViewModel
{

    public BaseShapeViewModel()
    {

    }

    private double left;
    public double Left
    {
        get => left;
        set => SetField(ref left, value, nameof(Left));
    }

    private double top;
    public double Top
    {
        get => top;
        set => SetField(ref top, value, nameof(Top));
    }

    private int width;
    public int Width
    {
        get => width;
        set => SetField(ref width, value, nameof(Width));
    }

    private int height;
    public int Height
    {
        get => height;
        set => SetField(ref height, value, nameof(Height));
    }

    private string fill;
    public string Fill
    {
        get => fill;
        set => SetField(ref fill, value, nameof(Fill));
    }

    private string text;
    public string Text
    {
        get => text;
        set => SetField(ref text, value, nameof(Text));
    }

}

其他ViewModels EllipseViewModel,RectangleViewModel继承自BaseShapeViewModel。我的MainViewModel看起来像

class MainViewModel : BaseViewModel
{
    public MainViewModel()
    {          
        BaseShapeViewModels = new ObservableCollection<BaseShapeViewModel>();                    
    }

    public ObservableCollection<BaseShapeViewModel> BaseShapeViewModels { get; set; }

    //public Canvas DesignerCanvas;
   
    private RelayCommand createUseCase;
    private RelayCommand createRectangle;

    public ICommand CreateUseCase
    {
        get
        {
            return createUseCase ?? 
                (
                    createUseCase = new RelayCommand(() => { AddUseCase(); })                    
                );
        }
    }
    public ICommand CreateRectangle
    {
        get
        {
            return createRectangle ??
                (
                    createRectangle = new RelayCommand(() => { AddRectangle(); })
                );
        }
    }

    private void AddUseCase()
    {
        Random rnd = new Random();
        
        int valueLeft = rnd.Next(0, 200);
        int valueTop = rnd.Next(0, 200);
        
        EllipseViewModel useCaseViewModel = new EllipseViewModel {Left=valueLeft,Top=valueTop, Height = 100, Width = 200, Fill="Blue"};
        BaseShapeViewModels.Add(useCaseViewModel);
  
    }

    private void AddRectangle()
    {
        Random rnd = new Random();
        
        int valueLeft = rnd.Next(0, 200);
        int valueTop = rnd.Next(0, 200);

        RectangleViewModel rectangleViewModel = new RectangleViewModel { Left = valueLeft, Top = valueTop, Height = 100, Width = 200, Fill = "Blue" };
        BaseShapeViewModels.Add(rectangleViewModel);
    }
}

我的MoveThumb.cs看起来像

 public class MoveThumb : Thumb
{
  
    public MoveThumb()
    {
        DragDelta += new DragDeltaEventHandler(this.MoveThumb_DragDelta);
    }

    private void MoveThumb_DragDelta(object sender, DragDeltaEventArgs e)
    {
        ContentControl designerItem = DataContext as ContentControl;
       
        if (designerItem != null)
        {
            Point dragDelta = new Point(e.HorizontalChange, e.VerticalChange);
            
            RotateTransform rotateTransform = designerItem.RenderTransform as RotateTransform;
            if (rotateTransform != null)
            {
                dragDelta = rotateTransform.Transform(dragDelta);
            }
            double left = Canvas.GetLeft(designerItem);
            double top = Canvas.GetTop(designerItem);
           
            Canvas.SetLeft(designerItem, left + dragDelta.X);
            Canvas.SetTop(designerItem, top + dragDelta.Y);       
        }

    }
}

并且知道我想说我不是Xaml的专家,但是我从第一个链接检查了材料并像这样创建MoveThumb.xaml

<ResourceDictionary 

<ControlTemplate x:Key="MoveThumbTemplate" TargetType="{x:Type s:MoveThumb}">
    <Rectangle Fill="Transparent"/>
</ControlTemplate>

在我创建ResizeDecorator和RotateDecorator之后,但是现在没关系,并创建DesignerItem.xaml

ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="MoveThumb.xaml"/>
    <ResourceDictionary Source="ResizeDecorator.xaml"/>
    <ResourceDictionary Source="RotateDecorator.xaml"/>
</ResourceDictionary.MergedDictionaries>

<!-- ContentControl style to move, resize and rotate items -->
<Style x:Key="DesignerItemStyle" TargetType="ContentControl">
    <Setter Property="MinHeight" Value="50"/>
    <Setter Property="MinWidth" Value="50"/>
    <Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
    <Setter Property="SnapsToDevicePixels" Value="true"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ContentControl">
                <Grid DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
                    <Control Name="RotateDecorator"
                             Template="{StaticResource RotateDecoratorTemplate}"
                             Visibility="Collapsed"/>
                    <s:MoveThumb Template="{StaticResource MoveThumbTemplate}"
                                 Cursor="SizeAll"/>
                    <Control x:Name="ResizeDecorator"
                             Template="{StaticResource ResizeDecoratorTemplate}"
                             Visibility="Collapsed"/>
                    <ContentPresenter Content="{TemplateBinding ContentControl.Content}"/>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="Selector.IsSelected" Value="True">
                        <Setter TargetName="ResizeDecorator" Property="Visibility" Value="Visible"/>
                        <Setter TargetName="RotateDecorator" Property="Visibility" Value="Visible"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

我尝试在我的MainWindow.xaml中为我的ContentControls绑定此样式

<Window.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Resources/DesignerItem.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Window.Resources>

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="100"/>
        <ColumnDefinition Width="*"/>
         </Grid.ColumnDefinitions>

    <StackPanel Background="Gray" Grid.RowSpan="2">
        <TextBlock Text="Shapes"                      
                   FontSize="18"
                   TextAlignment="Center" />
        <Button Content="Initial Node" />
        <Button Content="Final Node" />
        <Button Content="Line" />
        <Button Content="Action" />
        <Button Content="Decision Node" />
        <Button Content="Actor" />
        <Button Content="Class" Command="{Binding Path=CreateRectangle}"/>
        <Button Content="Use Case" Command="{Binding Path = CreateUseCase}"/>
    </StackPanel>

    <Grid Grid.Column="2">

        <ItemsControl ItemsSource="{Binding Path= BaseShapeViewModels}">

            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>

                    <Canvas/>

                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>

            <ItemsControl.Resources>
                <DataTemplate DataType="{x:Type viewModel:EllipseViewModel}">
                    <ContentControl
                                           Selector.IsSelected="True"
                                           Style="{StaticResource DesignerItemStyle}">

                        <Ellipse 
                                           Fill="{Binding Fill}"                                               
                                           IsHitTestVisible="False"/>

                    </ContentControl>
                </DataTemplate>

                <DataTemplate DataType="{x:Type viewModel:RectangleViewModel}">
                    <ContentControl             
                                           Selector.IsSelected="True"
                                           Style="{StaticResource DesignerItemStyle}">

                        <Rectangle 
                                           Fill="{Binding Fill}"                                              
                                           IsHitTestVisible="False"/>
                    </ContentControl>
                </DataTemplate>
            </ItemsControl.Resources>

            <ItemsControl.ItemContainerStyle>
                <Style TargetType="ContentPresenter">
                    <Setter Property="Canvas.Left" Value="{Binding Left, Mode=TwoWay}"/>
                    <Setter Property="Canvas.Top" Value="{Binding Top, Mode=TwoWay}"/>
                    <Setter Property="Width" Value="{Binding Width, Mode=TwoWay}"/>
                    <Setter Property="Height" Value="{Binding Height,Mode=TwoWay}"/>
                </Style>
            </ItemsControl.ItemContainerStyle>
        </ItemsControl>
        
    </Grid>
</Grid>
               
仿生代码

您无法在上拖动项目Canvas,因为您正在使用ItemsControl此控件将每个项目包装到一个容器中。您当前正在尝试拖动此容器的内容,而不是容器。实际上是位于中的容器Canvas

为了降低复杂性,我建议实现可扩展的自定义控件ItemsControl这样,您可以使项目容器本身可拖动。或者,您可以实现附加行为。

要提供调整大小和旋转用户界面的功能,建议使用Adorner

以下示例实现了一个自定义项目容器,该容器支持拖动和旋转以及一个Extended ItemsControl
由于draggable元素是ContentControl,因此不需要任何其他包装。这将简化用法,如下所示。

DraggableContentControl.cs
DraggableContentControl用作的项容器ItemsCanvas控制。
DraggableContentControl对于使用简单的Canvas面板也非常方便

<Canvas Width="1000" Height="1000">
  <DraggableContentControl Canvas.Left="50" 
                           Canvas.Top="50" 
                           Angle="45">
        <Rectangle Height="100" 
                   Width="100" 
                   Fill="Coral" />
  </DraggableContentControl>
</Canvas>

执行DraggableContentControl
设置DraggableContentControl.Angle允许旋转元素。
设置DraggableContentControl.WidthDraggableContentControl.Height允许缩放/调整内容大小(自动,因为内容被包装为Viewbox-参见Style下面的默认设置)。默认情况下,当size设置为时AutoDraggableContentControl将动态采用其内容的大小。

public class DraggableContentControl : ContentControl
{
  public static readonly DependencyProperty AngleProperty = DependencyProperty.Register(
    "Angle",
    typeof(double),
    typeof(DraggableContentControl),
    new PropertyMetadata(default(double), DraggableContentControl.OnAngleChanged));

  public double Angle
  {
    get => (double) GetValue(DraggableContentControl.AngleProperty);
    set => SetValue(DraggableContentControl.AngleProperty, value);
  }

  private RotateTransform RotateTransform { get; set; }
  private IInputElement ParentInputElement { get; set; }
  private bool IsDragActive { get; set; }
  private Point DragOffset { get; set; }

  static DraggableContentControl()
  {
    // Remove this line if you don't plan to define the default Style
    // inside the Generic.xaml file.
    DefaultStyleKeyProperty.OverrideMetadata(typeof(DraggableContentControl), new FrameworkPropertyMetadata(typeof(DraggableContentControl)));
  }

  public DraggableContentControl()
  {
    this.PreviewMouseLeftButtonDown += InitializeDrag_OnLeftMouseButtonDown;
    this.PreviewMouseLeftButtonUp += CompleteDrag_OnLeftMouseButtonUp;
    this.PreviewMouseMove += Drag_OnMouseMove;
    this.RenderTransformOrigin = new Point(0.5, 0.5);

    var transformGroup = new TransformGroup();
    this.RotateTransform = new RotateTransform();
    transformGroup.Children.Add(this.RotateTransform);
    this.RenderTransform = transformGroup;
  }

  #region Overrides of FrameworkElement

  public override void OnApplyTemplate()
  {
    base.OnApplyTemplate();

    // Parent is required to calculate the relative mouse coordinates.
    DependencyObject parentControl = this.Parent;
    if (parentControl == null
        && !TryFindParentElement(this, out parentControl)
        && !(parentControl is IInputElement))
    {
      return;
    }

    this.ParentInputElement = parentControl as IInputElement;
  }

  #endregion

  private void InitializeDrag_OnLeftMouseButtonDown(object sender, MouseButtonEventArgs e)
  {
    // Do nothing if disabled
    this.IsDragActive = this.IsEnabled;
    if (!this.IsDragActive)
    {
      return;
    }

    Point relativeDragStartPosition = e.GetPosition(this.ParentInputElement);

    // Calculate the drag offset to allow the content to be dragged 
    // relative to the clicked coordinates (instead of the top-left corner)
    this.DragOffset = new Point(
      relativeDragStartPosition.X - Canvas.GetLeft(this),
      relativeDragStartPosition.Y - Canvas.GetTop(this));

    // Prevent other controls from stealing mouse input while dragging
    CaptureMouse();
  }

  private void CompleteDrag_OnLeftMouseButtonUp(object sender, MouseButtonEventArgs e)
  {
    this.IsDragActive = false;
    ReleaseMouseCapture();
  }

  private void Drag_OnMouseMove(object sender, MouseEventArgs e)
  {
    if (!this.IsDragActive)
    {
      return;
    }

    Point currentPosition = e.GetPosition(this.ParentInputElement);

    // Apply the drag offset to drag relative to the 
    // initial mouse down coordinates (instead of the top-left corner)
    currentPosition.Offset(-this.DragOffset.X, -this.DragOffset.Y);
    Canvas.SetLeft(this, currentPosition.X);
    Canvas.SetTop(this, currentPosition.Y);
  }

  private static void OnAngleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
    (d as DraggableContentControl).RotateTransform.Angle = (double) e.NewValue;
  }

  private bool TryFindParentElement<TParent>(DependencyObject child, out TParent resultElement)
    where TParent : DependencyObject
  {
    resultElement = null;

    if (child == null)
    {
      return false;
    }

    DependencyObject parentElement = VisualTreeHelper.GetParent(child);

    if (parentElement is TParent)
    {
      resultElement = parentElement as TParent;
      return true;
    }

    return TryFindParentElement(parentElement, out resultElement);
  }
}

ItemsCanvas.cs
配置的ItemsControl使用DraggableContentControl作为项目的容器。

class ItemsCanvas : ItemsControl
{
  #region Overrides of ItemsControl

  protected override bool IsItemItsOwnContainerOverride(object item) => item is DraggableContentControl;

  protected override DependencyObject GetContainerForItemOverride() => new DraggableContentControl();

  #endregion
}

泛型

为默认的样式DraggableContentControlItemsCanvas

<Style TargetType="DraggableContentControl">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="DraggableContentControl">
        <Border Background="{TemplateBinding Background}"
                BorderBrush="{TemplateBinding BorderBrush}"
                BorderThickness="{TemplateBinding BorderThickness}">

          <!-- 
            Optional: wrapping the content into a Viewbox
            allows to automatically resize/scale the content 
            based on the container's (DraggableContentControl) size.
          -->
          <Viewbox Stretch="Fill">
            <ContentPresenter />
          </Viewbox>
        </Border>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>


<Style TargetType="ItemsCanvas">
  <Setter Property="ItemsPanel">
    <Setter.Value>
      <ItemsPanelTemplate>
        <Canvas />
      </ItemsPanelTemplate>
    </Setter.Value>
  </Setter>
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="ItemsCanvas">
        <Border Background="{TemplateBinding Background}"
                BorderBrush="{TemplateBinding BorderBrush}"
                BorderThickness="{TemplateBinding BorderThickness}">
          <ScrollViewer>
            <ItemsPresenter />
          </ScrollViewer>
        </Border>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

示例
此示例基于您的数据模型。

<Window>
  <Window.Resources>
    <DataTemplate DataType="{x:Type RectangleViewModel}">
      <Rectangle Height="{Binding Height}" 
                 Width="{Binding Width}"
                 Fill="{Binding Fill}" />
    </DataTemplate>
    <DataTemplate DataType="{x:Type EllipseViewModel}">
      <Ellipse Height="{Binding Height}" 
               Width="{Binding Width}"
               Fill="{Binding Fill}" />
    </DataTemplate>
  </Window.Resources>

  <ItemsCanvas ItemsSource="{Binding BaseShapeViewModels}" 
               Height="500" 
               Width="500">
    <ItemsCanvas.ItemContainerStyle>
      <Style TargetType="main:DraggableContentControl">
        <Setter Property="Canvas.Left" Value="{Binding Left, Mode=TwoWay}" />
        <Setter Property="Canvas.Top" Value="{Binding Top, Mode=TwoWay}" />
      </Style>
    </ItemsCanvas.ItemContainerStyle>
  </ItemsCanvas>
</Window>

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

如何在画布上重新绘制以调整大小而不会模糊?

来自分类Dev

如何在Android Studio中通过触摸来移动,旋转和调整文本大小?

来自分类Dev

UIGestureRecognizer用于旋转,调整大小,在UIView的边缘上移动

来自分类Dev

如何调整画布大小

来自分类Dev

画布和网格之间的差异,以移动,调整大小和旋转UIElement

来自分类Dev

画布和网格之间的差异,以移动,调整大小和旋转UIElement

来自分类Dev

如何在窗口调整大小时重绘画布

来自分类Dev

如何在Ubuntu Mate 15.04上使用ALT键+鼠标快速移动窗口或调整窗口大小?

来自分类Dev

画布粒子-在“窗口调整大小”上更新画布大小

来自分类Dev

在移动设备上如何防止徽标调整大小?

来自分类Dev

在窗口调整大小上动态调整画布大小

来自分类Dev

如何在调整大小和裁剪的位图中移动?

来自分类Dev

在tkinter画布上动态调整矩形大小

来自分类Dev

画布上的视频不断调整大小

来自分类Dev

如何处理窗口调整大小以使用 devicepixelratio 为移动设备显示画布

来自分类Dev

调整图像大小,然后在画布中旋转-javascript

来自分类Dev

图像旋转-根据图像的高度和宽度调整画布的大小

来自分类Dev

如何调整大小/移动分区?

来自分类Dev

如何在Python中随机移动画布上的球?

来自分类Dev

如何在移动和桌面设备上缩放画布

来自分类Dev

如何在flex容器中调整固定大小(画布)的元素的大小?

来自分类Dev

如何在p5 JS中正确旋转自定义形状并将其在画布上移动而不改变其角度?

来自分类Dev

如何在Android上向正确的方向移动旋转的视图?

来自分类Dev

画布上的Tkinter调整大小滚动条无法调整

来自分类Dev

画布上的Tkinter调整大小滚动条无法调整

来自分类Dev

如何在使用“ CarouselSlider”时相对于移动设备大小调整图像大小,并在图像上使按钮文字颤抖?

来自分类Dev

如何在不消除锯齿的情况下调整画布大小?

来自分类常见问题

如何在Mac OSX上使用终端调整图像大小?

来自分类Dev

如何在JsPlumb小部件上启用调整大小?

Related 相关文章

  1. 1

    如何在画布上重新绘制以调整大小而不会模糊?

  2. 2

    如何在Android Studio中通过触摸来移动,旋转和调整文本大小?

  3. 3

    UIGestureRecognizer用于旋转,调整大小,在UIView的边缘上移动

  4. 4

    如何调整画布大小

  5. 5

    画布和网格之间的差异,以移动,调整大小和旋转UIElement

  6. 6

    画布和网格之间的差异,以移动,调整大小和旋转UIElement

  7. 7

    如何在窗口调整大小时重绘画布

  8. 8

    如何在Ubuntu Mate 15.04上使用ALT键+鼠标快速移动窗口或调整窗口大小?

  9. 9

    画布粒子-在“窗口调整大小”上更新画布大小

  10. 10

    在移动设备上如何防止徽标调整大小?

  11. 11

    在窗口调整大小上动态调整画布大小

  12. 12

    如何在调整大小和裁剪的位图中移动?

  13. 13

    在tkinter画布上动态调整矩形大小

  14. 14

    画布上的视频不断调整大小

  15. 15

    如何处理窗口调整大小以使用 devicepixelratio 为移动设备显示画布

  16. 16

    调整图像大小,然后在画布中旋转-javascript

  17. 17

    图像旋转-根据图像的高度和宽度调整画布的大小

  18. 18

    如何调整大小/移动分区?

  19. 19

    如何在Python中随机移动画布上的球?

  20. 20

    如何在移动和桌面设备上缩放画布

  21. 21

    如何在flex容器中调整固定大小(画布)的元素的大小?

  22. 22

    如何在p5 JS中正确旋转自定义形状并将其在画布上移动而不改变其角度?

  23. 23

    如何在Android上向正确的方向移动旋转的视图?

  24. 24

    画布上的Tkinter调整大小滚动条无法调整

  25. 25

    画布上的Tkinter调整大小滚动条无法调整

  26. 26

    如何在使用“ CarouselSlider”时相对于移动设备大小调整图像大小,并在图像上使按钮文字颤抖?

  27. 27

    如何在不消除锯齿的情况下调整画布大小?

  28. 28

    如何在Mac OSX上使用终端调整图像大小?

  29. 29

    如何在JsPlumb小部件上启用调整大小?

热门标签

归档