Silverlight 业务应用程序的拖放






4.75/5 (3投票s)
本文介绍了如何在业务应用中使用 Silverlight 拖放机制。
引言
在业务网站的开发过程中,我们在使用 Silverlight 时遇到了一些障碍,特别是拖放功能的实现,这在互联网上无法轻易找到解决方案。有很多关于使用 Silverlight 拖放与文件系统实用程序的例子,但是当业务应用中的对象来自数据库而不是文件系统时,该如何实现拖放呢?我们找到的许多示例都忽略了业务应用需要准确知道移动了什么对象以及从哪里移动,并且需要更新数据库来反映这些变化的需求。
本文提出了我们为解决“知道哪个对象被移动了及其详细信息”的问题而提出的解决方案。由于 Silverlight 的功能非常强大,可能还有其他方法可以解决这个问题。
背景
本文假设您至少对 Silverlight 有一定的了解,特别是版本 4 和 C#。
动态拖放设置的必备条件
我们采用的技术在表示对象方面相当简单。ListBox
作为主要容器,ListBoxItem
用于显示业务对象。由于我们需要一定的灵活性来显示额外信息,例如列标题以及启用拖放功能,因此还需要包含一些其他内容。为了处理这个问题,我们使用 StackPanel
作为列容器,其第一个子元素是列标题,紧接着是 ListBoxDragDropTarget
,后者又包含 ListBox
。通过这种层级结构的容器,视觉上的拖放功能就已经可以工作了,这意味着您可以将一个列表框中的项拖放到另一个列表框中,而无需任何额外的代码。唯一的局限性是数据库不会因此更新。
创建这种层级结构的代码量出人意料地少,并且结果模型是完全动态的,这意味着您可以拥有任意数量的列以及每列中的任意数量的项。XAML 代码相当简短,如下所示:
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Margin="20,0,0,0" HorizontalAlignment="Left"
VerticalAlignment="Top" Text="Silverlight Business Drag and Drop"
FontSize="16"/>
<Grid x:Name="DragDropRoot" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" Margin="40,40,0,0"/>
</Grid>
由于所有剩余工作都通过代码完成,因此代码中的列和项比 XAML 要复杂一些,因为您需要确保所有属性都已设置,以使视觉效果正确。
让我们开始构建列本身。使用上述提到的层级结构,我们从 StackPanel
开始,并添加一个列标题来向用户指示该列的用途。创建 ListBoxDragDropTarget
只需三行代码,然后您添加一个 ListBox
来实际存放项。
// Column Parent
StackPanel column1 = new StackPanel();
column1.Margin = new Thickness(0, 0, 0, 0);
column1.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
column1.VerticalAlignment = System.Windows.VerticalAlignment.Top;
column1.Width = 150;
column1.Height = 400;
// Column Heading
TextBlock hdr1 = new TextBlock();
hdr1.Text = "Column 1";
hdr1.Height = 30;
hdr1.Margin = new Thickness(0, 0, 0, 0);
hdr1.HorizontalAlignment = System.Windows.HorizontalAlignment.Center;
hdr1.VerticalAlignment = System.Windows.VerticalAlignment.Top;
// Enabling Drag and Drop
ListBoxDragDropTarget source = new ListBoxDragDropTarget();
source.Drop += new Microsoft.Windows.DragEventHandler(dest_Drop);
source.AllowDrop = true;
// Setting up the Parent of the items that can be dragged and dropped
ListBox box1 = new ListBox();
box1.Name = "Column1";
box1.Margin = new Thickness(1, 1, 0, 0);
box1.Background = new SolidColorBrush(Colors.Transparent);
box1.FlowDirection = System.Windows.FlowDirection.LeftToRight;
box1.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
box1.VerticalAlignment = System.Windows.VerticalAlignment.Stretch;
box1.Width = 140;
box1.Height = 360;
// Heirarchy of Items
s1.Children.Add(txt1);
i1.Content = s1;
// Items within this column are added here
box1.Items.Add(i1);
column1.Children.Add(hdr1);
source.Content = box1;
column1.Children.Add(source);
DragDropRoot.Children.Add(column1);
注意:在上述代码段的底部,层级结构被构建起来,使所有对象都能正常工作。
在设置好列的背景后,列仍然是空的,没有可以演示的项。虽然在此演示中我只显示了两列,但您的应用程序完全可以根据需要拥有任意数量的列。
由于业务对象(包括列本身以及列中的项)来自数据库,并且通常每个对象都有一个唯一的标识符,因此我们可以将该唯一标识符作为每个项名称的一部分。在以下示例中,我们展示了如何构建单个项,使其可以为任意数量的项完成。每个 ListBoxItem
代表一个业务对象,因此与 ListBoxItem
关联的 Name
可以包含您的唯一标识符,或者包含名称加标识符(如果您要表示多个对象),以确保您可以在其被拖放到另一个列时识别它。
在每个 ListBoxItem
内部,通常会有多个项来描述该项。因此,我通常使用 StackPanel
(或 Grid
),然后它包含将要在用户界面中显示的每个项。在 StackPanel
中,您可能会像我在此演示的那样使用 TextBlock
,或者使用任意数量的其他对象(图像、按钮等)来完成用户的视图。
最后三行代码用于将所有项收集到一个包中,然后可以将其添加到上面定义的列中。
ListBoxItem i2 = new ListBoxItem();
i2.BorderBrush = new SolidColorBrush(Colors.Red);
i2.BorderThickness = new Thickness(1);
i2.Name = "2";
// This would be your unique key so that you can recognize
// the item when it is dropped on another column
StackPanel s2 = new StackPanel();
s2.Background = new SolidColorBrush(Colors.Magenta);
s2.Width = 130;
s2.Height = 100;
s2.FlowDirection = System.Windows.FlowDirection.LeftToRight;
TextBlock txt2 = new TextBlock();
txt2.Width = 129;
txt2.Height = 90;
txt2.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
txt2.VerticalAlignment = System.Windows.VerticalAlignment.Top;
txt2.TextWrapping = TextWrapping.Wrap;
txt2.Text = "This is another source item that can be dragged and dropped";
// Add other Controls as needed to the StackPanel
s1.Children.Add(txt1);
i1.Content = s1;
box1.Items.Add(i1);
这就定义了列及其中的项。接下来是识别拖放操作所需的代码。虽然这只需要几行代码,但由于需要从复杂的参数中提取数据,因此很难知道如何从 DragEventArgs
参数中获取项的名称。通过提取列名和被移动项的名称,您将拥有完整的详细信息,以便能够更新数据库或应用任何必要的规则。
void dest_Drop(object sender, Microsoft.Windows.DragEventArgs e)
{
var format = e.Data.GetFormats()[0];
ItemDragEventArgs dragItem = e.Data.GetData(format) as ItemDragEventArgs;
bs = (ListBox)dragItem.DragSource;
ListBoxDragDropTarget b = (ListBoxDragDropTarget)sender;
bd = (ListBox)b.Content;
string WhoWasIt = ((ListBoxItem)bs.SelectedItem).Name;
MessageBox.Show("Item " + WhoWasIt + " was moved from " +
bs.Name + " to " + bd.Name);
}
上面的前两行代码提取了正在拖动的对象,包括它来自哪里。然后,中间三行将 DragSource
作为源对象,并将 sender
的 Content
作为目标对象。一旦您获得了这些信息,您就可以获取 SelectedItem
作为正在拖动的 ListBoxItem
,并提取名称以获取您的业务对象的唯一代码(上面的 WhoWasIt
变量显示了对象的原始名称)。有了这些信息,您就可以使用该对象从一个列移动到另一个列的信息来更新您的数据库。如果您还需要结果的顺序,则需要添加一个 StoryBoard
事件,以便此事件有时间完成,然后您可以查看目标 ListBox
以找出该项在 ListBox
中的最终位置。鉴于您已经拥有对象名称,在目标 ListBox
中找到它很容易。
如果您需要拖放多个项,则需要遍历 SelectedItems
集合,而不是使用 SelectedItem
(如果您拖动多个项,SelectedItem
只会给出第一个项)。
使用代码
要使用此代码,您需要考虑一些问题,例如将使用您数据库中的哪些信息来生成每个 ListBoxItem
,以及在拖放操作完成后需要哪些信息来更新您的数据库。由于此示例不包含数据库访问代码,也不包含构建多个对象所需的循环,因此您需要将至少项的创建包装在一个循环中,并且如果有很多列,可能还需要将列的创建也包装起来。
关注点
在 Silverlight 中找到您需要的信息可能有些困难,但一旦找到,只需几行代码就可以轻松完成许多棘手的问题。