WPF Caliburn.Micro框架下如何实现运行时动态节点拖拽功能
WPF+Caliburn.Micro动态节点拖拽实现方案
1 替换布局容器为绝对定位容器
你当前使用的WrapPanel为流式排列容器,不支持自定义位置的拖拽移动,首先修改ItemsControl的面板为Canvas:
<ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas Background="Transparent" /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel>
需要给Canvas设置透明背景,否则无法正常接收鼠标事件。
2 绑定Item容器的位置属性
在ItemsControl的ItemContainerStyle中绑定节点ViewModel的坐标属性,适配MVVM模式:
<ItemsControl.ItemContainerStyle> <Style TargetType="ContentPresenter"> <Setter Property="Canvas.Left" Value="{Binding X}" /> <Setter Property="Canvas.Top" Value="{Binding Y}" /> </Style> </ItemsControl.ItemContainerStyle>
你需要为节点的ViewModel添加X、Y两个可通知属性(实现INotifyPropertyChanged接口)。
3 利用路由事件处理拖拽逻辑
WPF的冒泡路由事件会自动向上传递到父容器,不需要为每个动态生成的节点单独绑定事件,直接在ItemsControl上监听三个核心事件即可,配合Caliburn.Micro的事件绑定可以直接把逻辑放到ViewModel中。
修改后的完整XAML代码如下:
<!-- Column 2 : Design panel--> <StackPanel x:Name="DesignPanelState" Grid.Row="1" Grid.Column="2" Grid.RowSpan="2" > <ItemsControl x:Name="Nodes" Background="Transparent" cal:Message.Attach="[Event MouseLeftButtonDown] = [Action OnDragStart($eventArgs, $source)]; [Event MouseMove] = [Action OnDragging($eventArgs, $source)]; [Event MouseLeftButtonUp] = [Action OnDragEnd($eventArgs, $source)]"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <Canvas Background="Transparent" MinWidth="800" MinHeight="600" /> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemContainerStyle> <Style TargetType="ContentPresenter"> <Setter Property="Canvas.Left" Value="{Binding X}" /> <Setter Property="Canvas.Top" Value="{Binding Y}" /> </Style> </ItemsControl.ItemContainerStyle> <ItemsControl.ItemTemplate> <DataTemplate> <Border BorderBrush="Black" BorderThickness="1" Margin ="3" Padding="3"> <StackPanel Orientation="Vertical"> <Button x:Name="NodeIdx" Content="{Binding NodeName}" MinWidth="50" MinHeight="50"/> </StackPanel> </Border> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> </StackPanel>
4 ViewModel拖拽逻辑实现
// 全局拖拽状态变量 private bool _isDragging; private NodeViewModel _draggingNode; private Point _dragStartPoint; private Point _nodeStartPoint; public void OnDragStart(MouseButtonEventArgs e, IInputElement source) { // 获取点击的节点对象 var hit = VisualTreeHelper.HitTest((Visual)source, e.GetPosition((IInputElement)source)); if (hit.VisualHit is FrameworkElement element && element.DataContext is NodeViewModel node) { _isDragging = true; _draggingNode = node; _dragStartPoint = e.GetPosition(Nodes.ItemsPanelRoot); _nodeStartPoint = new Point(node.X, node.Y); Mouse.Capture((IInputElement)source); e.Handled = true; } } public void OnDragging(MouseEventArgs e, IInputElement source) { if (!_isDragging || _draggingNode == null) return; var currentPos = e.GetPosition(Nodes.ItemsPanelRoot); var offsetX = currentPos.X - _dragStartPoint.X; var offsetY = currentPos.Y - _dragStartPoint.Y; // 更新节点坐标 _draggingNode.X = _nodeStartPoint.X + offsetX; _draggingNode.Y = _nodeStartPoint.Y + offsetY; e.Handled = true; } public void OnDragEnd(MouseButtonEventArgs e, IInputElement source) { if (_isDragging) { _isDragging = false; _draggingNode = null; Mouse.Capture(null); e.Handled = true; } }
注意事项
- 如果需要限制节点拖拽范围,在更新
X、Y属性时增加边界判断即可 - 按钮控件会抢占鼠标事件,如果需要点击按钮本身也能拖拽,可以给按钮加上
IsHitTestVisible="False",或者单独处理按钮的鼠标事件
内容的提问来源于stack exchange,提问作者Ahmed Kamal
相关产品推荐
相关产品推荐

