You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.28 18:15:03