ChildWindow在所有应用领域中都是必不可缺的好工具,小到一个弹出的确认对话框;大到包含众多复杂参数、选项及数据列表的资源窗口,都可以通过它来分类实现。ChildWindow的好处是可以实现拖动及伸缩达到随意布局,更高级的同样可以实现类似最大化、最小化等高级布局功能。

目前Silverlight3.0中自带的ChildWindow感觉仅仅是作为一个富Dialog的范畴应用,我们可以通过在项目中右键->添加->新建项来选择创建一个ChildWindow控件:

然后通过类似如下仅仅几行代码即可激活(弹出)一个300*300ChildWindow

            QXChildWindow childWindow = new QXChildWindow() {

                Width = 300,

                Height = 300,

            };

            childWindow.Show();

如上图,该ChildWindow将从整个Silverlight应用程序窗口的正中间弹出,并屏蔽掉所有输入事件;此时,如果您突发奇想尝试通过代码连续弹出多个ChildWindow,结果会让您很失望,这些ChildWindow最终只会按顺序一个接一个的出来(即只有关闭掉前一个才会弹出下一个),而无法同时存在于窗口内。

于是,我找到园子里Kinglee写的这篇文章,里面介绍了外国高人Tim Heuer为我们提供的Non-Modal Used ChildWindow组件(非模式(模态)使用子窗体——Tim Heuer称之为浮动窗体[FloatableWindow])【下载】【应用程序模板】。大家可以从该文章中的演示Demo体验到此控件的强大~。但是,当我想用它大展拳脚时却发现此控件还是无法实现最大化的界面灵活性。比如它的关闭按钮固定在右上且一直占据着一个位置(带背景且于ZIndex顶部呈现);就算通过设置其TilteObject)值等于一张图片,结果仍然会出现Border在整个ChildWindow的外边;外加如果要使用该控件,我们还得引用它的dll(54k),这对于Silverlight来说似乎大了些。诸多种种的不便迫使我不得不打开源码来看个究竟。

通过对源码的分析,我大致将此控件分成4个关键部分:第一个为顶部用于位移拖动的标题栏;第二个为标题栏右上角的关闭按钮;第三个为中间大面积的内容区域;第四个为右下角的伸缩拖放按钮。

对应的实现原理也很简单,例如拖动标题栏实现ChildWindow的移动是通过对标题栏同时注册鼠标按下、鼠标弹起及鼠标移动事件,并于其中附上相关逻辑来实现;而伸缩拖放则是通过鼠标点击起点及时时的位置之间的逻辑来实现。

按照以上原理,下面我将一步一步为大家演示在Silverlight中如何实现一个简易的可拖动可伸缩的QQ聊天对话面板。

首先创建一个名为QXChildWindow的用户控件,该控件xaml包含着以上描述的4个关键部件HeadBodyCloseButtonResizer

<UserControl x:Class="Silverlight.Control.QXChildWindow"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" MouseLeftButtonDown="UserControl_MouseLeftButtonDown" Width="555" Height="518">

    <Canvas x:Name="Root">

        <!--用于拖动的Title-->

        <Canvas x:Name="Head" Width="553" Height="24" MouseLeftButtonDown="Head_MouseLeftButtonDown" MouseLeftButtonUp="Head_MouseLeftButtonUp" MouseMove="Head_MouseMove">

            <!--关闭按钮-->

            <Image x:Name="CloseButton" Width="26" Height="16" Canvas.Left="514"/>

        </Canvas>

        <!--主体部分-->

        <Canvas x:Name="Body" Width="553" Height="494" Canvas.Top="24">

            <!--尺寸伸缩按钮-->

            <Image x:Name="Resizer" Width="9" Height="9" Canvas.Left="544" Canvas.Top="484" MouseLeftButtonDown="Resizer_MouseLeftButtonDown" MouseLeftButtonUp="Resizer_MouseLeftButtonUp" MouseMove="Resizer_MouseMove" />

        </Canvas>

    </Canvas>

</UserControl>

头部的拖动功能实现需要Head_MouseLeftButtonDownHead_MouseLeftButtonUp、和Head_MouseMove3个事件配合起来实现,即首先通过MouseLeftButtonDown获取鼠标拖动起点位置,且通过CaptureMouse标记鼠标已在Head上处于了按下状态:

        private void Head_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {

            ……

            Head.CaptureMouse();

            isMouseCaptured = true;

            clickPoint = e.GetPosition(sender as UIElement);

        }

过程中如果放开了鼠标,我们则取消压下标记并ReleaseMouseCapture

        private void Head_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) {

                Head.ReleaseMouseCapture();

                isMouseCaptured = false;

        }

如果一直是鼠标按着不动,则我们移动鼠标时将触发拖动整个控件通过TransformGroup进行偏移移动:

        private void Head_MouseMove(object sender, MouseEventArgs e) {

            if (isMouseCaptured) {

                //假如拖出了窗体范围则return

                if (Application.Current != null && Application.Current.RootVisual != null &&

                    (e.GetPosition(Application.Current.RootVisual).X < 0 || e.GetPosition(Application.Current.RootVisual).Y < 0)) {

                    return;

                }

                TransformGroup transformGroup = this.RenderTransform as TransformGroup;

                if (transformGroup == null) {

                    transformGroup = new TransformGroup();

                    transformGroup.Children.Add(this.RenderTransform);

                }

                TranslateTransform t = new TranslateTransform() {

                    X = e.GetPosition(this).X - this.clickPoint.X,

                    Y = e.GetPosition(this).Y - this.clickPoint.Y

                };

                if (transformGroup != null) {

                    transformGroup.Children.Add(t);

                    this.RenderTransform = transformGroup;

                }

            }

        }

是不是很简单?其实浮动可拖动自定义窗体是可以如此简单就实现的。这里还有一个极其重要的细节,即该用户控件整体的UserControl_MouseLeftButtonDown事件:

        /// <summary>

        /// 类静态ZIndex值,通过它可以轻松实现所有QXChildWindow实例被点击后置顶呈现

        /// </summary>

        static int z;

        private void UserControl_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {

            z++;

            Canvas.SetZIndex(this, z);

        }

当我们在任意一个QXChildWindow实例上点击左键时,此实例将前置到最前方。仅仅不过使用了一个控件级静态变量z外加如此简单的逻辑即实现了众多弹出的QXChildWindow实例之间的轻松层次管理。

至于整个控件的伸缩,我是通过对所有子控件按比例缩放来实现的;当然,在实际开发中并非一定都按如此,关键要看您的自定义ChildWindow是如何布局与设定的。这部分代码就不罗列了,大家可以自行下载本节的源码进行参考练习。

完全的自定义ChildWindow不仅仅可以实现更灵活的界面布局,而且作为自定义控件的一种,在使用和管理方面都非常的便捷。

最后是本节的在线Demo

 

WPF/Silverlight
作者:深蓝色右手
出处:http://alamiye010.cnblogs.com/
本系列目录及源码下载:点击进入(欢迎加入WPF/Silverlight小组 WPF/Silverlight博客团队)
本文版权归作者和博客园共有,欢迎转载。但未经作者同意必须保留此段声明,且在文章页面显著位置给出原文连接,否则保留追究法律责任的权利。
posted on 2009-11-23 18:02  深蓝色右手  阅读(10366)  评论(17编辑  收藏  举报