I want to wrap some Rectangles on a Canvas.The details of rectangles are in a list in my viewmodel.I succeeded drawing the rectangles,but failed to make them movable.In my eventhandler Canvas.GetLeft() and Canvas.SetLeft dosen't work.
here is my ItemsControl:
<ItemsControl Width="1920"
Height="1080"
ItemsSource="{Binding Controls,Mode=TwoWay}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<UniformGrid Rows="2"
PreviewMouseUp="Container_Control_MouseUp"
PreviewMouseMove="Container_Control_MouseMove"
PreviewMouseDown="Container_Control_MouseDown">
<Rectangle Width="{Binding Width}"
Height="{Binding Height}"
Fill="Blue" />
<TextBlock Grid.Row="1"
Text="move it" />
</UniformGrid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Top"
Value="{Binding X}" />
<Setter Property="Canvas.Left"
Value="{Binding Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
and here is my eventhandlers,they don't work with elements in a itemscontrol:
public partial class ControlsView : UserControl
{
bool _isMouseDown = false;
Point _mouseDownPosition;
Point _mouseDownControlPosition;
public ControlsView(ControlsViewModel vm)
{
this.DataContext = vm;
InitializeComponent();
}
private void Container_Control_MouseUp(object sender, MouseButtonEventArgs e)
{
var c = sender as UIElement;
_isMouseDown = false;
c.ReleaseMouseCapture();
}
private void Container_Control_MouseMove(object sender, MouseEventArgs e)
{
if (_isMouseDown)
{
var c = sender as UIElement;
var pos = e.GetPosition(this);
var dp = pos - _mouseDownPosition;
Canvas.SetLeft(c, _mouseDownControlPosition.X + dp.X);
Canvas.SetTop(c, _mouseDownControlPosition.Y + dp.Y);
}
}
private void Container_Control_MouseDown(object sender, MouseButtonEventArgs e)
{
var c = sender as UIElement;
_isMouseDown = true;
_mouseDownPosition = e.GetPosition(this);
_mouseDownControlPosition = new Point(double.IsNaN(Canvas.GetLeft(c)) ? 0 : Canvas.GetLeft(c), double.IsNaN(Canvas.GetTop(c)) ? 0 : Canvas.GetTop(c));
c.CaptureMouse();
}
}
The UniformGrid
is not the direct child of the Canvas
. You must attach the mouse event handlers to the item container. You can use the ItemsControl.ItemContainerStyle
to register them using an EventSetter
:
<ItemsControl Width="1920"
Height="1080"
ItemsSource="{Binding Controls}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<UniformGrid Rows="2">
<Rectangle Width="{Binding Width}"
Height="{Binding Height}"
Fill="Blue" />
<TextBlock Grid.Row="1"
Text="move it" />
</UniformGrid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<EventSetter Event="PreviewMouseLeftButtonDown"
Handler="ItemContainer_PreviewMouseLeftButtonDown" />
<EventSetter Event="PreviewMouseMove"
Handler="ItemContainer_PreviewMouseMove" />
<Setter Property="Canvas.Top"
Value="{Binding X}" />
<Setter Property="Canvas.Left"
Value="{Binding Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
You can also simplify your algorithm:
// The initial mouse offset relative to the container position
private Point mousePositionOffset;
private void ItemContainer_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var itemContainer = sender as IInputElement;
_ = Mouse.Capture(itemContainer);
this.mousePositionOffset = e.GetPosition(itemContainer);
}
private void ContentPresenter_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton is not MouseButtonState.Pressed
|| sender is not UIElement itemContainer)
{
Mouse.Capture(null);
return;
}
var canvas = (Canvas)VisualTreeHelper.GetParent(itemContainer);
Point currentMousePosition = e.GetPosition(canvas);
Canvas.SetLeft(itemContainer, currentMousePosition.X - this.mousePositionOffset.X);
Canvas.SetTop(itemContainer, currentMousePosition.Y - this.mousePositionOffset.Y);
}