如果将 UIElement 从 Panel a 拖放到 Panel b 失败,如何回退?

How to fall back if drag and drop an UIElement from Panel a to Panel b fails?

我尝试使用以下演示代码将 UIElement(在本例中为 Button)从一个面板拖放到另一个面板:

[MainWindow.xaml]

<Window x:Class="WpfDemo.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="WPF Demonstration" 
    MinHeight="480" 
    MinWidth="640">
<Grid x:Name="mainGrid" Margin="3,3,3,3" ShowGridLines="False" UseLayoutRounding="True">

    <!-- Row definition for 3 rows -->
    <Grid.RowDefinitions>
        <!-- Row0 header -->
        <RowDefinition Height="Auto"></RowDefinition>
        <!-- Row1 content -->
        <RowDefinition Height="*"></RowDefinition>
        <!-- Row2 footer -->
        <RowDefinition Height="Auto"></RowDefinition>
    </Grid.RowDefinitions>

    <!-- Column definition for 5 colums -->
    <Grid.ColumnDefinitions>
        <!-- Col0 left panel -->
        <ColumnDefinition Width="*" MinWidth="100" MaxWidth="300"></ColumnDefinition>
        <!-- Col1 for GridSplitter left  -->
        <ColumnDefinition Width="Auto"></ColumnDefinition>
        <!-- Col2 center panel -->
        <ColumnDefinition Width="*" MinWidth="100"></ColumnDefinition>
        <!-- Col3 for GridSplitter right -->
        <ColumnDefinition Width="Auto"></ColumnDefinition>
        <!-- Col4 right panel -->
        <ColumnDefinition Width="*" MinWidth="100" MaxWidth="300"></ColumnDefinition>
    </Grid.ColumnDefinitions>

    <!-- Grid content Row0 header -->
    <!-- TODO: Move Button Margin into Style -->
    <Button Grid.Column="0" Grid.Row="0" Margin="0,0,0,3" HorizontalAlignment="Left">Button 1</Button>
    <Button Grid.Column="2" Grid.Row="0" Margin="0,0,0,3" HorizontalAlignment="Center">Button 2</Button>
    <Button Grid.Column="4" Grid.Row="0" Margin="0,0,0,3" HorizontalAlignment="Right">Button 3</Button>

    <!-- Grid content Row2 content -->
    <!-- Grid content Col0 -->
    <StackPanel Grid.Row="1" Grid.Column="0" AllowDrop="True" DragEnter="Panel_DragEnter" Drop="Panel_Drop">
        <StackPanel.Background>
            <SolidColorBrush Color="LightGreen"/>
        </StackPanel.Background>
        <Label HorizontalAlignment="Left">Stack Panel</Label>
        <Button x:Name="btnDragDrop" PreviewMouseDown="btnDragDrop_PreviewMouseDown">Drag me to an other container</Button>
    </StackPanel>
    <DockPanel Grid.Row="1" Grid.Column="2" AllowDrop="True" DragEnter="Panel_DragEnter" Drop="Panel_Drop">
        <DockPanel.Background>
            <SolidColorBrush Color="LightBlue"/>
        </DockPanel.Background>
        <Label>Dock Panel</Label>
    </DockPanel>
    <WrapPanel Grid.Row="1" Grid.Column="4" AllowDrop="True" DragEnter="Panel_DragEnter" Drop="Panel_Drop">
        <WrapPanel.Background>
            <SolidColorBrush Color="LightPink"/>
        </WrapPanel.Background>
        <Label>Wrap Panel</Label>
    </WrapPanel>
    <!-- Grid content Row4 footer -->
        <StackPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="5">
            <Label x:Name="lbl" HorizontalContentAlignment="Center" Margin="0,3,0,0">
                <Label.Background>
                    <SolidColorBrush Color="LightGray"/>
                </Label.Background>
                <Label.Content>&#8656; Label footer &#8658;</Label.Content>
            </Label>
        </StackPanel>
    <!-- GridSplitter for Col1 left  -->
    <GridSplitter x:Name="myGsLeft" Grid.Column="1" Grid.RowSpan="2" VerticalAlignment="Stretch" HorizontalAlignment="Center" Width="3" DragDelta="GridSplitter_DragDelta" DragCompleted="GridSplitter_DragCompleted"/>


    <!-- GridSplitter for Col3 right -->
    <GridSplitter x:Name="myGsRight" Grid.Column="3" Grid.RowSpan="2" VerticalAlignment="Stretch" HorizontalAlignment="Center" Width="3" DragDelta="GridSplitter_DragDelta" DragCompleted="GridSplitter_DragCompleted"/>
</Grid>

[MainWindow.xaml.cs]

    public partial class MainWindow : Window
{
    /// <summary>
    /// Provides a reusable ToolTip object used by GridSplitter_DragDelta and GridSplitter_DragComplete
    /// </summary>
    private ToolTip flyingToolTip = new ToolTip();

    public MainWindow()
    {
        InitializeComponent();
    }

    /// <summary>
    /// Shows a tooltip with the actual width of the left grid column near the registered GridSplitter column while it is dragging
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void GridSplitter_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
    {
        if (sender.GetType() != typeof(GridSplitter))
        { return; }

        ShowActualWidthToolTip(gs: (sender as GridSplitter), tt: flyingToolTip);
    }

    private void GridSplitter_DragCompleted(object sender, DragCompletedEventArgs e)
    {
        flyingToolTip.IsOpen = false;
    }
    /// <summary>
    /// ShowActualWidthToolTip shows actual width of the left and right column near the GridSplitter column, so one can split two columns precisely
    /// </summary>
    /// <param name="gs"></param>
    /// <param name="tt"></param>
    // TODO: MainWindow.ShowActualWidthToolTip seems to be to tricky for reusability, maybe one find a more scaleable solution
    private void ShowActualWidthToolTip(GridSplitter gs, ToolTip tt)
    {
        // If the GridSplitter isn't positioned correctly in a seperate column between two other columns, drop functionality
        Grid parentGrid = gs.Parent as Grid;
        double? leftColumnActualWidth = null;
        double? rightColumnActualWidth = null;
        try 
        {
            leftColumnActualWidth = parentGrid.ColumnDefinitions[(Grid.GetColumn(gs) - 1)].ActualWidth;
            rightColumnActualWidth = parentGrid.ColumnDefinitions[Grid.GetColumn(gs) + 1].ActualWidth;

        }
        catch (ArgumentOutOfRangeException ex)
        {
            MessageBox.Show("Something went wrong in your GridSplitter layout. Splitter must been set in a column between the two columns who method tries to evaluate actual width. \n\n" + ex.Message, "Error", MessageBoxButton.OK);
        }

        tt.Content = String.Format("\u21E4 Width left {0} | {1} Width right \u21E5", leftColumnActualWidth, rightColumnActualWidth);
        tt.PlacementTarget = this;
        tt.Placement = PlacementMode.Relative;
        tt.HorizontalOffset = (Mouse.GetPosition(this).X - (tt.ActualWidth / 2));
        tt.VerticalOffset = (Mouse.GetPosition(this).Y + 10);
        tt.IsOpen = true;
        return;
    }
    /// <summary>
    /// Prepares a button for moving from one panel to another panel
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    /// TODO: Implement fallback, if button wasn't drop down to another panel
    private void btnDragDrop_PreviewMouseDown(object sender, MouseButtonEventArgs e)
    {
        Button btn = sender as Button;

        // TODO: This should be done on another place, but where?
        Panel parent = btn.Parent as Panel;
        parent.Children.Remove(btn);

        DragDrop.DoDragDrop(btn, btn, DragDropEffects.Move);
    }
    /// <summary>
    /// Prepares a panel for moving in an element via drag and drop
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Panel_DragEnter(object sender, DragEventArgs e)
    {
        e.Effects = DragDropEffects.Move;
    }
    /// <summary>
    /// 
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void Panel_Drop(object sender, DragEventArgs e)
    {
       Panel panel = sender as Panel;
       panel.Children.Add(((UIElement)e.Data.GetData(typeof(Button))));
    }
}

面板之间的拖放按预期方式工作。但是如果按钮掉落在其他任何地方,则可以销毁该按钮。如您所见,我删除了 btnDragDrop_PreviewMouseDown(object sender, MouseButtonEventArgs e) 中的 btn.parent,但那一定是错误的地方。所以我需要一点提示在哪里做。它应该按以下方式运行:

如果放置目标不是面板,则不会发生任何事情,也不会从面板的子列表中删除。

来源:WpfDemo on github.com

你可以做的是定义两个私有字段,一个将保存拖动的 button 另一个将保存源 panel:

private Button tmpButton=null;
private Panel sourcePanel=null;

然后更改您的 btnDragDrop_PreviewMouseDown 处理程序以保存对拖动的 button 和源 panel 的引用,而不是将其删除:

 private void btnDragDrop_PreviewMouseDown(object sender, MouseButtonEventArgs e)
    {            
        var btn = sender as Button;
        tmpButton = btn;            
        var parent = btn.Parent as Panel;
        sourcePanel = parent;            
        DragDrop.DoDragDrop(btn, btn, DragDropEffects.Move);
    }

最后,您 Panel_Drop 处理程序将从源中删除 btm 并将其添加到目标中 panel

 private void Panel_Drop(object sender, DragEventArgs e)
    {
        Panel panel = sender as Panel;
        if (sourcePanel != null && tmpButton!=null)
        {
            sourcePanel.Children.Remove(tmpButton);
            panel.Children.Add(((UIElement)e.Data.GetData(typeof(Button))));
            sourcePanel = null;
            tmpButton = null;
        }                      
    }