Mostraré cómo crear una aplicación WPF con patrón MVVM para 2D-Poliline con vértices arrastrables.
PointViewModel.cs
public class PointViewModel: ViewModelBase
{
public PointViewModel(double x, double y)
{
this.Point = new Point(x, y);
}
private Point point;
public Point Point
{
get { return point; }
set
{
point = value;
OnPropertyChanged("Point");
}
}
}
La clase ViewModelBase
incluye solamente una implementación de la interfaz INotifyPropertyChanged
. Esto es necesario para reflejar los cambios de la propiedad clr en la representación visual.
LineViewModel.cs
public class LineViewModel
{
public LineViewModel(PointViewModel start, PointViewModel end)
{
this.StartPoint = start;
this.EndPoint = end;
}
public PointViewModel StartPoint { get; set; }
public PointViewModel EndPoint { get; set; }
}
Tiene referencias a puntos, por lo que los cambios serán recibidas automáticamente.
MainViewModel.cs
public class MainViewModel
{
public MainViewModel()
{
this.Points = new List<PointViewModel>
{
new PointViewModel(30, 30),
new PointViewModel(60, 100),
new PointViewModel(50, 120)
};
this.Lines = this.Points.Zip(this.Points.Skip(1).Concat(this.Points.Take(1)),
(p1, p2) => new LineViewModel(p1, p2)).ToList();
}
public List<PointViewModel> Points { get; set; }
public List<LineViewModel> Lines { get; set; }
}
Contiene una muestra de datos de puntos y líneas
MainVindow.xaml
<Window.Resources>
<ItemsPanelTemplate x:Key="CanvasPanelTemplate">
<Canvas/>
</ItemsPanelTemplate>
<Style x:Key="PointListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding Point.X}"/>
<Setter Property="Canvas.Top" Value="{Binding Point.Y}"/>
</Style>
<DataTemplate x:Key="LineTemplate">
<Line X1="{Binding StartPoint.Point.X}" X2="{Binding EndPoint.Point.X}" Y1="{Binding StartPoint.Point.Y}" Y2="{Binding EndPoint.Point.Y}" Stroke="Blue"/>
</DataTemplate>
<DataTemplate x:Key="PointTemplate">
<view:PointView />
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Lines}" ItemsPanel="{StaticResource CanvasPanelTemplate}" ItemTemplate="{StaticResource LineTemplate}"/>
<ItemsControl ItemsSource="{Binding Points}" ItemContainerStyle="{StaticResource PointListBoxItem}" ItemsPanel="{StaticResource CanvasPanelTemplate}"
ItemTemplate="{StaticResource PointTemplate}"/>
</Grid>
Aquí hay una gran cantidad de trucos. En primer lugar, estos ItemsControls
se basan no en el StackPanel
vertical, sino en el Canvas
.El ItemsControl
de puntos aplica una plantilla de contenedor especial con el objetivo de colocar elementos en las coordenadas necesarias. Pero ItemsControl
de líneas no requieren tales plantillas, y es extraño en algún momento. Dos últimas DataTemplates son obvias.
PointView.xaml
<Ellipse Width="12" Height="12" Stroke="Red" Margin="-6,-6,0,0" Fill="Transparent"/>
márgenes izquierdo y superior son iguales a la mitad de la Width
y la Height
. Tenemos un Fill
transparente porque esta propiedad no tiene un valor predeterminado y los eventos del mouse no funcionan.
Eso es casi todo. Solo queda la funcionalidad de arrastrar y soltar.
PointView.xaml.cs
public partial class PointView : UserControl
{
public PointView()
{
InitializeComponent();
this.MouseLeftButtonDown += DragSource_MouseLeftButtonDown;
this.MouseMove += DragSource_MouseMove;
}
private bool isDraggingStarted;
private void DragSource_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
this.isDraggingStarted = true;
}
private void DragSource_MouseMove(object sender, MouseEventArgs e)
{
if (isDraggingStarted == true)
{
var vm = this.DataContext as PointViewModel;
var oldPoint = vm.Point;
DataObject data = new DataObject("Point", this.DataContext);
DragDropEffects effects = DragDrop.DoDragDrop(this, data, DragDropEffects.Move);
if (effects == DragDropEffects.None) //Drag cancelled
vm.Point = oldPoint;
this.isDraggingStarted = false;
}
}
MainVindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
this.AllowDrop = true;
this.DragOver += DropTarget_DragOver;
}
private void DropTarget_DragOver(object sender, DragEventArgs e)
{
var vm = e.Data.GetData("Point") as PointViewModel;
if (vm != null)
vm.Point = e.GetPosition(this);
}
}
Así que la muestra se realiza mediante 2 archivos XAML y 3 ViewModels.
@Tom Aconsejaría intentar algunas cosas en código en lugar de en Xaml si Xaml te está volviendo loco. –
@Tom Sugiero comenzar con un control 'Canvas' y jugar con agregar elementos y cambiar sus posiciones programáticamente. Puede tener una idea de cómo trabajar con rutas, pero sin tener que lidiar con el head-boiler de Xaml al mismo tiempo. –
Quien vote para cerrar, por favor explique por qué. Es una pregunta válida Gracias. –