如果将 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>⇐ Label footer ⇒</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,但那一定是错误的地方。所以我需要一点提示在哪里做。它应该按以下方式运行:
如果放置目标不是面板,则不会发生任何事情,也不会从面板的子列表中删除。
你可以做的是定义两个私有字段,一个将保存拖动的 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;
}
}
我尝试使用以下演示代码将 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>⇐ Label footer ⇒</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,但那一定是错误的地方。所以我需要一点提示在哪里做。它应该按以下方式运行:
如果放置目标不是面板,则不会发生任何事情,也不会从面板的子列表中删除。
你可以做的是定义两个私有字段,一个将保存拖动的 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;
}
}