zoukankan      html  css  js  c++  java
  • 利用Silverlight实现类似iGoogle的浮动层拖拽效果

    既然Silverlight号称是Ajax杀手,而且相比javascript更接近桌面应用,那么这种拖拽的效果自然是手到擒来。

    布局容器选择:
    Silverlight提供了Canvas、Grid、StackPanel三种布局容器,基本能满足各种需要。个人习惯是用Grid做框架布局,具体内容利用StackPanel自组织,而Canvas则在某些特定场合,比如打字游戏的界面中使用。

    一方面在自适应浏览器大小的角度上,Grid提供了两方面的自由度,StackPanel提供了一个方向的自由度,而Canvas没有自由度,也就不能随这浏览器大小改变而自动适应大小;另一方面,Grid和StackPanel都可以不用直接指定坐标进行布局,而Canvas则需要指定确定的坐标,维护起来肯定麻烦;第三点,则是主要针对于可拖拽效果的,StackPanel是唯一可以通过添加、删除内部控件而自动调整内部其他控件位置的布局容器。

    所以最后决定:用Grid做外部布局,每一列都设定ColumnDefinition,再在每一列添加一个StackPanel做为单列的容器:

    <uc:ContainerGrid.ColumnDefinitions>
        <ColumnDefinition></ColumnDefinition>
        <ColumnDefinition></ColumnDefinition>
        <ColumnDefinition></ColumnDefinition>
    </uc:ContainerGrid.ColumnDefinitions>
    <uc:ContainerPanel Grid.Column="0" x:Name="LeftPanel">
    </uc:ContainerPanel>
    <uc:ContainerPanel Grid.Column="1" x:Name="CenterPanel">
    </uc:ContainerPanel>
    <uc:ContainerPanel Grid.Column="2" x:Name="RightPanel">
    </uc:ContainerPanel>这里的ContainerGrid和ContainerPanel分别继承自Grid和Panel,主要是添加了几个方便以后查询以及扩展的属性。

    可拖拽控件:
    可拖拽控件可以继承自任何控件,这里用的是UserControl。

    public partial class DragableGrid : UserControl那么对于DragableGrid,最主要的事件是三个:MouseLeftButtonDown(左键按下),MouseLeftButtonUp(左键抬起),MouseMove(左键移动)。MouseLeftButtonDown标识拖拽开始,MouseLeftButtonUp说明拖拽结束,而MouseMove则是拖拽过程触发。此外,考虑到当鼠标移动到控件可移动范围之外的情况,还有MouseLeave事件,和MouseLeftButtonUp的意义一样。所以需要在构造函数中添加它们的处理逻辑:

    this.MouseLeftButtonDown += new MouseButtonEventHandler(DragableGrid_MouseLeftButtonDown);
    this.MouseLeftButtonUp += new MouseButtonEventHandler(DragableGrid_MouseLeftButtonUp);
    this.MouseMove += new MouseEventHandler(DragableGrid_MouseMove);
    this.MouseLeave += new MouseEventHandler(DragableGrid_MouseLeave);左键按下(拖拽开始):
    void DragableGrid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        //标示开始拖拽
        IsDraging = true;
        //鼠标的起始位置(控件内部)
        _beginPoint = e.GetPosition(this);
        //鼠标相对于Grid的位置
        Point marginPoint = e.GetPosition(_parentGrid);
        ContainerPanel parentPanel = this.Parent as ContainerPanel;
        shadowGrid = new ShadowGrid(this, parentPanel);
        //设置初始Margin。
        Thickness beginMargin = new Thickness(marginPoint.X - _beginPoint.X, marginPoint.Y - _beginPoint.Y, _parentGrid.ActualWidth - this.ActualWidth - marginPoint.X + _beginPoint.X, _parentGrid.ActualHeight - this.ActualHeight - marginPoint.Y + _beginPoint.Y);
        (this.Parent as Panel).Children.Remove(this);
        if (this.Parent == null)
        {
            _parentGrid.Children.Add(this);
        }
        this.SetValue(Grid.ColumnSpanProperty, _parentGrid.PanelCount);
        this.Margin = beginMargin;
    }
    首先设置IsDraging标识位,然后分别获取鼠标点击点相对于控件左上角以及相对于ContainerGrid左上角的位移,从而获得可拖拽控件相对于ContainerGrid的Margin值。注意鼠标相对控件左上角的位移在拖拽过程中是不变的,所以作为可拖拽控件的一个私有变量存储起来。然后将DragableGrid从父Panel中移除,添加到ContainerGrid中,使它能在整个Grid的范围内移动。

    这里仿照iGoogle和其他类似的可拖拽框架的模式,当拖拽一个可拖拽控件时,会在可拖拽控件的原处位置添加一个背影控件(ShadowGrid),用来占位:

    public ShadowGrid(DragableGrid originGrid,ContainerPanel parentPanel)
    {
        InitializeComponent();
        this._orginGrid = originGrid;
        //设置影子Grid和原始Grid的样式一致。
        this.Width = _orginGrid.ActualWidth;
        this.Height = _orginGrid.ActualHeight;
        this.Text = _orginGrid.Text;
        this.Margin = _orginGrid.Margin;
        //设置影子Grid的父Panel并插入。
        _vIndex = parentPanel.Children.IndexOf(_orginGrid);
        _hIndex = parentPanel.Index;
        parentPanel.Children.Insert(_vIndex + 1, this);
    }而且这里影子控件和可拖拽控件互相引用,方便以后的操作。

    鼠标移动:
    先判断IsDraging 标识位,之后根据鼠标的新位置设置控件的Margin:

    if (IsDraging)
    {
        Point newPosition = e.GetPosition(_parentGrid);
        this.Margin = new Thickness(newPosition.X - _beginPoint.X, newPosition.Y - _beginPoint.Y, _parentGrid.ActualWidth - this.ActualWidth - newPosition.X + _beginPoint.X, _parentGrid.ActualHeight - this.ActualHeight - newPosition.Y + _beginPoint.Y);
    这里利用了鼠标相对控件的位移,也就是_beginPoint是使用不变的。

    接下来就是最麻烦的部分:根据新位置,判断控件的拖拽状况,这里有左右移动和上下移动两种情况。

    首先是比较简单的左右移动:

    //向左移
    if (this.Margin.Left < _parentGrid.PanelWidth * (shadowGrid.HIndex - 0.5))
    {
        if (shadowGrid.HIndex > 0)
        {
            ContainerPanel panel = this._parentGrid.ChildPanels[shadowGrid.HIndex - 1];
            shadowGrid.MoveTo(panel);
        }
    }
    //向右移
    else if (this.Margin.Left > _parentGrid.PanelWidth * (shadowGrid.HIndex + 0.5))
    {
        if (shadowGrid.HIndex < _parentGrid.PanelCount - 1)
        {
            ContainerPanel panel = this._parentGrid.ChildPanels[shadowGrid.HIndex + 1];
            shadowGrid.MoveTo(panel);
        }
    }_parentGrid.PanelWidth代表每一列的宽度,而shadowGrid.HIndex则是shadowGrid所在列的序号,shadowGrid的MoveTo方法实现了将shadowGrid水平插入另一个Panel中:

    public void MoveTo(ContainerPanel panel)
    {
        ParentPanel.Children.Remove(this);

        if (panel.Children.Count > _vIndex)
        {
            panel.Children.Insert(_vIndex, this);
        }
        else
        {
            _vIndex = panel.Children.Count;
            panel.Children.Add(this);
        }
        this._hIndex = panel.Index;
    }其中_vIndex代表shadowGrid在所在列中的位置。

    之后则是比较复杂的上下移动:

    //向上移
    bool hasMove = false;
    if (shadowGrid.VIndex > 0)
    {
        Control upControl = shadowGrid.ParentPanel.Children[shadowGrid.VIndex - 1] as Control;
        Point upPoint = e.GetPosition(upControl);
        if ((upPoint.Y - _beginPoint.Y) < upControl.ActualHeight / 2)
        {
            shadowGrid.MoveTo(shadowGrid.VIndex - 1);
            hasMove = true;
        }
    }
    //向下移
    if (!hasMove && shadowGrid.VIndex < shadowGrid.ParentPanel.Children.Count - 1)
    {
        Control downControl = shadowGrid.ParentPanel.Children[shadowGrid.VIndex + 1] as Control;
        Point downPoint = e.GetPosition(downControl);
        if ((_beginPoint.Y - downPoint.Y) < this.ActualHeight / 2)
        {
            shadowGrid.MoveTo(shadowGrid.VIndex + 1);
        }
    }由于Silverlight中并没有像WPF那样提供直接获取两个控件相对位移的方法,而由于控件的大小未必固定,所以不能像左右移动那样根据Panel的宽度来计算是否移动。幸好我们可以利用鼠标的MouseEventArgs e来获取鼠标当前位置和其他控件的位移,从而间接算出两个控件的位置。shadowGrid的另一个重载的MoveTo方法实现了将shadowGrid在一个Panel中从一个位置移到另一个位置:

    public void MoveTo(int newVIndex)
    {
        ContainerPanel panel = ParentPanel;
        panel.Children.Remove(this);

        _vIndex = newVIndex;
        panel.Children.Insert(_vIndex, this);
    }鼠标左键抬起或离开(拖拽结束)
    抬起和离开的逻辑是一样的:

    void DragableGrid_MouseLeave(object sender, MouseEventArgs e)
    {
        _parentGrid.Children.Remove(this);
        RealeaseShadow();
        IsDraging = false;
    }public void RealeaseShadow()
    {
        if (this.shadowGrid != null)
        {
            this.shadowGrid.Release();
        }
        this.shadowGrid = null;
    }ShadowGrid的Release方法,将自己从父容器中移出,并将原始的可拖拽控件放入ShadowGrid所在的位置。

    public void Release()
    {
        this._orginGrid.Margin = this.Margin;
        ContainerPanel panel = ParentPanel;
        panel.Children.Remove(this);
        panel.Children.Insert(_vIndex, _orginGrid);
    }注意事项:
    1.由于鼠标移动是一个很快的过程,MouseMove事件可能触发多次,如果移动过快,就可能出现运算不及,导致鼠标脱离可拖拽控件的情况,之后鼠标再返回控件,可能会出现问题。所以在一些地方,需要做一些看似多余的检测,比如RealeaseShadow方法中,检测shadowGrid 是否为空。
    2.由于使用了StackPanel和Grid,里面的控件默认会采用Strech样式,即充满整个容器,对于Panel中的控件,就没有指定它的Width,所以这里用到的都是控件的ActualWidth属性,也就是实际显示的宽度,这个不可设的属性会在控件Rend之后获得。
    3.如果外层的Grid的大小会改变,那么就不能利用分别设置上下左右的Margin来设置控件的位置,否则控件的大小也会随着Grid的大小而改变,那么就需要先确保控件的Width和Height有确定的值,然后设置控件的左对齐和上对齐,那么此时控件的Margin-Right和Maring-Bottom无效,则只要通过指定Margin-Top,和Margin-left,就可以对控件在Grid里做定位。
    this.Width = this.ActualWidth;
    this.Height = this.ActualHeight;
    this.VerticalAlignment = VerticalAlignment.Top;
    this.HorizontalAlignment = HorizontalAlignment.Left;
    //设置初始Margin。
    Thickness beginMargin = new Thickness(marginPoint.X - _beginPoint.X, marginPoint.Y - _beginPoint.Y,0,0);

    源代码下载:可拖拽Silverlight控件源码

    REF:http://blog.csdn.net/tuoxie5431/archive/2009/03/01/3946278.aspx

  • 相关阅读:
    Java 简单算法--打印乘法口诀(只使用一次循环)
    Java简单算法--求100以内素数
    ubuntu 16.04 chrome flash player 过期
    java 网络API访问 web 站点
    java scoket (UDP通信模型)简易聊天室
    leetcode1105 Filling Bookcase Shelves
    leetcode1140 Stone Game II
    leetcode1186 Maximum Subarray Sum with One Deletion
    leetcode31 Next Permutation
    leetcode834 Sum of Distances in Tree
  • 原文地址:https://www.cnblogs.com/dotfun/p/1773135.html
Copyright © 2011-2022 走看看