微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

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

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

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

WPF: Hydrate Canvas with Draggable Controls at Runtime

当然,我遇到了与上述作者类似的问题:当我绘制github.com/org/repo/actions时,它使用随机坐标正确绘制,但是当我尝试移动它时,它不会移动!并且当我调试类ContentControl时,我发现我的MoveThumb尚未获得其ContentControl。但是我认为它应该以{{1​​}}作为Parent。我知道我应该覆盖某些系统/基本方法,但是我不明白应该覆盖什么以及如何覆盖它。也许有人有主意?

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

Canvas

其他viewmodels Ellipseviewmodel,Rectangleviewmodel继承自BaseShapeviewmodel。 我的Mainviewmodel看起来像

Parent

我的MoveThumb.cs看起来像

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,nameof(Top));
    }

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

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

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

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

}

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

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

        Rectangleviewmodel rectangleviewmodel = new Rectangleviewmodel { Left = valueLeft,Top = valuetop,Fill = "Blue" };
        BaseShapeviewmodels.Add(rectangleviewmodel);
    }
}

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

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

    }
}

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

<ResourceDictionary 

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

解决方法

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

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

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

以下示例实现了一个自定义项目容器,该容器支持拖动和旋转以及扩展的ItemsControl
由于可拖动元素是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)。默认情况下,当大小设置为Auto时,DraggableContentControl将动态采用其内容的大小。

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
}

Generic.xaml

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

BionocCode解决方案工作正常,但我发现了另一种方法,我只更改了两种方法

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

            /*use this*/
            var model = designerItem.DataContext as BaseShapeViewModel;
            model.Left += dragDelta.X;
            model.Top += dragDelta.Y;

            /* instead of this*/
            //double left = Canvas.GetLeft(item);
            //double top = Canvas.GetTop(item);
            //Canvas.SetLeft(item,left + e.HorizontalChange);
            //Canvas.SetTop(item,top + e.VerticalChange);
        }

    }

查看最后3行,我能够从ContentControl中获取ViewModel并更改其属性,当我更改它们时,ObservableCollection中ViewModel对象的属性也会更改,并且一切呈现效果都很好

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。