【Win 10 应用开发】UI Composition 札记(八):用 XamlLight 制作灯光效果

前面老周已介绍过灯光的使用,如果你忘了,请用九牛二虎之力猛点击这里去复习一下。本篇老周再介绍另一种添加灯光的方法,这种方法是专为 XAML 元素而设计的,可以很方便地为可视化元素添加灯光效果。

不知道大伙伴是否发现,UIElement 类公开了一个 Lights 属性(15063,v1703,或更高版本),它是一个列表,可以添加若干个 XamlLight 对象。通过这个属性,我们也能为 XAML 可视化元素设置灯光。

虽然我们看到 XamlLight 类有构造函数,但是,它不是直接使用的,因为它一些重要成员都声明为 protected,这在外部类是无法访问的。所以,你必须自己派生出一个自定义的类型,然后为这些成员设置相关的内容。为什么这样做呢?你想想啊,如果只实例化这个类,运行时根本不可能知道你要用啥灯光,故你得自己去动手写。

先看看这些重要成员是啥。

protected virtual void OnConnected(UIElement newElement);

protected virtual void OnDisconnected(UIElement oldElement);

protected virtual string GetId();

// 这个属性用来设置要用的灯光对象
protected CompositionLight CompositionLight { get; set; }

先介绍一下长得最帅的那个—— CompositionLight,我们在代码中根据自己的需要,使用 Compositor 来创建灯光实例,还记得前面学过的内容吧,然后把灯光实例赋值给 CompositionLight 属性。

那么,在啥时候赋值呢?我们看到,有两个对应的方法—— OnConnected 和 OnDisconnected。当咱们把自定义的类型实例添加到 UIElement 元素的 Lights 列表中时,就会调用 OnConnected 方法,而跟随方法参数传递的就是这个可视化元素的实例。比如,你把这个自定义类实例添加到 Grid 的 Lights 列表中,那么传给 newElement 参数的就是这个 Grid。相反地,当我们自定义的XamlLight对象从 Lights 列表中被移除时,会调用 OnDisconnected 方法,这时候我们就应该把 CompositionLight 属性所占用的资源清理掉,如调用 Dispose 方法,然后把属性设置为 null。

估计你也看到了,XamlLight 类还有一个成员—— GetId 方法,你也必须实现这个成员,然后你要返回一个字符串,这个字符串必须能够唯一地代表你实现的这个类,最好的办法是返回这个类的类名,因为这个一般都能唯一的(我说的是包括命名空间名字的)。那这个字符串又在哪里用呢?你再看,XamlLight 类有两对静态方法:

AddTargetElement 与 RemoveTargetElement:用来指定哪些元素能被灯光照见,Add 进去的可视化对象就能被照亮的,而 Remove 后的对象是不会被灯光照亮的,你会看到,方法的第一个参数是一个字符串类型的 lightId,对的,这就是上面我们实现 GetId 方法的作用了。

AddTargetBrush 与 RemoveTargetBrush:使灯光照射到画刷上,而不是照到可视化对象上,用法也一样,Add了的画刷会被照亮,Remove后的画刷是不被照亮的。

在应用灯光对象时你要记住,把它添加到 UIElement 对象的 Lights 列表中,仅仅说明为这个灯光安排了照射空间。这个前面在介绍灯光时老周讲过的,比如 PointLight ,它是点状光,你必须安排一个 CoordinateSpace 对象,作为灯光的参数,这好比你把蜡烛放在一个小房子中,你不能把蜡烛放在野外,因为空矿的环境会严重削弱光线,所以,你得安排一个参照空间。此处,把自定义 XamlLight 放进 Lights 列表中,仅仅相当于你安排了这个 UI 元素作为参照空间而已,而这个空间内的子元素并不会真正应用灯光,所以,你必须调用静态的 AddTargetElement 方法,指明里面的哪些子元素会被照亮。

如果你把灯光对象添加到 Grid 的 Lights 列表中,表明灯光是以这个 Grid 为参照空间,然后你调用 AddTargetElement 方法,并把这个 Grid 元素传给方法,这说明整个 Grid 元素包括它的子元素都会被照亮的。如果你只希望 Grid 元素中某个子元素被照亮,就把子元素传给 AddTargetElement 方法。

好,说了那么多 F 话,咱们动手试试。

从 XamlLight 类派生,我们自定义一个灯源,叫 MyCustLight。

    class MyCustLight : XamlLight
    {
        protected override string GetId()
        {
            return GetType().FullName;
        }

        protected override void OnConnected(UIElement newElement)
        {
            // 创建灯光
            var compositor = Window.Current.Compositor;
            PointLight light = compositor.CreatePointLight();
            // 设置灯光参数
            light.Color = Colors.LightGreen;
            light.Offset = new System.Numerics.Vector3(240f, 80f, 20f);
            light.Intensity = 5.3f;
            // 为属性赋值
            CompositionLight = light;
            // 这一句很重要
            XamlLight.AddTargetElement(GetId(), newElement);
        }

        protected override void OnDisconnected(UIElement oldElement)
        {
            // 这一句是对应的,Add了之后就要Remove
            XamlLight.RemoveTargetElement(GetId(), oldElement);
            // 释放资源
            CompositionLight.Dispose();
            CompositionLight = null;
        }
    }

GetId 方法我就不解释了,你能看懂的。说说其他成员,调用 OnConnected 方法时,咱们创建一个 PointLight 实例,需要用到的 Compositor 实例可以从  Window.Current.Compositor 属性中获取,同一个窗口下的UI元素都共用的。

设置好点状光的各个参数,然后记得赋值给 CompositionLight 属性,这个一定不要忘了,不然灯光是不起作用的。下面这一行也是重要的。

  XamlLight.AddTargetElement(GetId(), newElement);

这意思就是,把 Lights 所属的 UI 元素加入到可被照亮区域,包括其子元素。

当光源从 Lights 列表中移除时,要调用 OnDisconnected 方法,在这个方法中,要把可照亮区域 Remove 掉,还要把 CompositionLight 属性所引用的资源清理掉。

            // 这一句是对应的,Add了之后就要Remove
            XamlLight.RemoveTargetElement(GetId(), oldElement);
            // 释放资源
            CompositionLight.Dispose();
            CompositionLight = null;

好了,现在,自定义灯源已经做好,咱们用 XAML 对象试试。

        <Grid Name="layout" Background="Black">
            <Grid Margin="10">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="auto"/>
                    <ColumnDefinition Width="*"/>
                </Grid.ColumnDefinitions>
                <Image Source="Assets/1.jpg" Width="300" Height="300"/>
                <StackPanel Grid.Column="1" Margin="13">
                    <RichTextBlock Foreground="Yellow">
                        <Paragraph>
                            <Run Text="姓名:" FontWeight="Bold"/>
                            <Run Text="八戒" />
                        </Paragraph>
                        <Paragraph>
                            <Run Text="性别:" FontWeight="Bold"/>
                            <Run Text="男"/>
                        </Paragraph>
                        <Paragraph>
                            <Run Text="民族:" FontWeight="Bold"/>
                            <Run Text="猪"/>
                        </Paragraph>
                        <Paragraph>
                            <Run Text="优点:" FontWeight="Bold"/>
                            <Run Text="勤劳、憨厚、和善"/>
                        </Paragraph>
                    </RichTextBlock>
                </StackPanel>
            </Grid>
        </Grid>

为了方便看到灯光效果,我把 Grid 的背景弄成黑色。子元素中有文字,也有二师兄的头像,有关二师兄的照片,你可以网上找,二师兄那么出名,网上有他的照片。

首先,让大家看看,没有应用灯光效果时,二师兄的身份证。

二师兄还是那么帅。然后,转到代码视图,为 Grid 应用灯光。

        public MainPage()
        {
            this.InitializeComponent();
            MyCustLight light = new MyCustLight();
            layout.Lights.Add(light);
        }

如果你觉得用 XAML 添加更好,可以用 XAML 来添加。

        <Grid Name="layout" Background="Black">
            <Grid.Lights>
                <local:MyCustLight/>
            </Grid.Lights>
           ……
        </Grid>

注意,两种方法取一即可,不要重复加。

现在,我们看看,二师兄被灯光照射后会变成什么样的。

二师兄依旧很帅。

这时候你又在想,我只想让灯光照射二师兄的头像,而不希望照射到右边的文本,那咱办呢?上面老周说过了,决定元素是否被照射,就是 AddTargetElement 方法在起作用。我们把 MyCustLight 类改一下就行了,弄一个附加属性,值为 bool 类型,如果某个子元素设为 true,就照亮它,如果为 false 就不照亮了。

来,咱们把 MyCustLight 类改一下。

因为这个类我在声明时没有加 public 修饰符,默认就成为 internal ,非 public 类无法在 XAML 中使用附加属性,所以,得先把它改为公共类。

 public class MyCustLight : XamlLight

然后,GetId 方法的实现也要改,咱们知道,依赖项属性或附加属性都是静态成员,而我们在响应附加属性更改的回调方法中要用到 XamlLight  的 AddTargetElement 或 RemoveTargetElement 方法,这样是要提供这个 ID 字符串的,所以,我们可以定义一个静态属性,返回这个 ID,然后在 GetId 方法访问一下就可以了。

        protected override string GetId()
        {
            return LightID;
        }
        // 通过静态属性返回
        private static string LightID => typeof(MyCustLight).FullName;

下面是重点,我们为这个类注册一个名为 IsLightEnabled 的附加属性。

  public static readonly DependencyProperty IsLightEnabledProperty = DependencyProperty.RegisterAttached("IsLightEnabled", typeof(bool), typeof(MyCustLight), new PropertyMetadata(false, OnIsLightEnabledPropertyChanged));

附加属性封装比较特殊,用 Get**** 和 Set**** 两个静态方法,也必须是 public 的。

        public static void SetIsLightEnabled(DependencyObject obj, bool val)
        {
            obj.SetValue(IsLightEnabledProperty, val);
        }
        public static bool GetIsLightEnabled(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsLightEnabledProperty);
        }

注意,Get*** 和 Set*** 中的 *** 一定要与你刚刚注册的附加属性的名字一致,不要写错了。

当这个附加属性被修改后,我们通过回调方法来判断属性值是否为 true,若为真,就应用灯光效果,若为假,就移除灯光效果。

        private static void OnIsLightEnabledPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            bool b = (bool)e.NewValue;
            UIElement targetUiele = d as UIElement;
            if (b)  //如要照亮
            {
                XamlLight.AddTargetElement(LightID, targetUiele);
            }
            else   //如果不想照亮
            {
                XamlLight.RemoveTargetElement(LightID, targetUiele);
            }
        }

在 OnConnected 和  OnDisconnected 方法的实现中,把刚才的 AddTargetElement 和 RemoveTargetElement 方法的调用代码删除掉,因为咱们已经用附加属性处理了。

现在,MyCustLight 类应该变成这个样子。

    public class MyCustLight : XamlLight
    {
        protected override string GetId()
        {
            return LightID;
        }
        // 通过静态属性返回
        private static string LightID => typeof(MyCustLight).FullName;

        public static readonly DependencyProperty IsLightEnabledProperty = DependencyProperty.RegisterAttached("IsLightEnabled", typeof(bool), typeof(MyCustLight), new PropertyMetadata(false, OnIsLightEnabledPropertyChanged));

        public static void SetIsLightEnabled(DependencyObject obj, bool val)
        {
            obj.SetValue(IsLightEnabledProperty, val);
        }
        public static bool GetIsLightEnabled(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsLightEnabledProperty);
        }

        private static void OnIsLightEnabledPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            bool b = (bool)e.NewValue;
            UIElement targetUiele = d as UIElement;
            if (b)  //如要照亮
            {
                XamlLight.AddTargetElement(LightID, targetUiele);
            }
            else   //如果不想照亮
            {
                XamlLight.RemoveTargetElement(LightID, targetUiele);
            }
        }

        protected override void OnConnected(UIElement newElement)
        {
            // 创建灯光
            var compositor = Window.Current.Compositor;
            PointLight light = compositor.CreatePointLight();
            // 设置灯光参数
            light.Color = Colors.LightGreen;
            light.Offset = new System.Numerics.Vector3(240f, 80f, 20f);
            light.Intensity = 5.3f;
            // 为属性赋值
            CompositionLight = light;
        }

        protected override void OnDisconnected(UIElement oldElement)
        {
            // 释放资源
            CompositionLight.Dispose();
            CompositionLight = null;
        }
    }

最后,在 XAML 代码中,让 Image 元素接收光源,而右边的文本不接收光源。

            <Grid Margin="10">
                ……
                <Image Source="Assets/1.jpg" Width="300" Height="300" local:MyCustLight.IsLightEnabled="True"/>
                <StackPanel Grid.Column="1" Margin="13" local:MyCustLight.IsLightEnabled="False">
                    <RichTextBlock Foreground="Yellow">
                        ……
                    </RichTextBlock>
                </StackPanel>
            </Grid>

再次运行,就可以看到,只有二师兄的猪头上有灯光,而右边的文本不会被灯光照亮。

OK,今天的内容就扯到这里,88。

时间: 2024-10-10 20:38:29

【Win 10 应用开发】UI Composition 札记(八):用 XamlLight 制作灯光效果的相关文章

【Win 10 应用开发】打印UI元素

原文:[Win 10 应用开发]打印UI元素 Windows App支持将UI界面进行打印的功能,这与浏览器中的打印网页的用途相近,其好处就是“所见即所得”,直接把界面上呈现的内容打印下来,比重新创建打印图像方便得多. 要在通用App中实现打印,主要依靠以下几个类型: PrintManager:位于Windows.Graphics.Printing命名空间,主要负责显示打印对话框,设置打印源等操作.在使用时,首先调用GetForCurrentView静态方法得到一个PrintManager实例:

【Win 10应用开发】如何知道当前APP在哪个平台设备上运行

[Win 10应用开发]如何知道当前APP在哪个平台设备上运行 在做Win10开发的时候,我们可能经常会需要获得当前程序在在哪个平台设备上运行,用于UI和相关API的调用,那么可以通过什么方式知道当前APP运行的平台呢? 今天这里提供两个方法给大家做参考: 方法一:DeviceFamily 通过Windows.System.Profile.AnalyticsInfo.VersionInfo.DeviceFamily,来获取当前的平台设备,目前只可以得到两个值Windows.Mobile或Wind

【Win 10应用开发】实现全屏播放的方法

原文:[Win 10应用开发]实现全屏播放的方法 有人会问,以前的MediaElement控件不是有现成的一排操作按钮吗?而且可以直接进入全屏播放.是的,我们知道,以往的Store App都是在全屏模式下运行的,只要MediaElement控件填满整个窗口,就等于全屏播放了,但是,Win10应用是窗口化的,将MediaElement控件的IsFullWindow属性设置为true后,就会这样: 从上面的截图看,MediaElement控件只是覆盖整个窗口而已,并没有实现全屏.那有办法让它全屏播放

【Win 10应用开发】如何知道UAP在哪个平台上运行

原文:[Win 10应用开发]如何知道UAP在哪个平台上运行 面向22世纪的现代化应用程序可以同时在多种设备上运行,于是有朋友会有一个疑问:有时候,我们还真的需要判断一下,UAP应用程序在哪个平台上运行.尽管大多情况下我们不必要这样做,但某些特殊情况还得考虑.比如一串数据列表,我希望如果在桌面上运行时就以横向列表展现:但要是运行在手机上就以纵向列表展现. 也就是说,其实我们只需分析两种情况即可: 一.桌面.(台式机.笔记本.平板.游戏机.发广告专用机.导航器……) 二.移动环境.其实就是手机.

【Win 10 应用开发】RTM版的UAP项目解剖

原文:[Win 10 应用开发]RTM版的UAP项目解剖 Windows 10 发布后,其实SDK也偷偷地在VS的自定义安装列表中出现了,今天开发人员中心也更新了下载.正式版的SDK在API结构上和以前预览的时候是一样的,只是版本变成10240罢了,所以大家不要问老周有什么新的API. API虽然没变,但VS中的应用程序项目是有了新变化.毕竟以前都是预览的,而现在是“正规军”,以前练兵时都用土豆枪,现在都是真刀真枪干了,故而应用程序项目是有变化的. 以前老周跟大家讲的修改项目模板,去掉遥测类库的

【Win 10 应用开发】Toast通知激活应用——前台&amp;后台

原文:[Win 10 应用开发]Toast通知激活应用--前台&后台 老周最近热衷于讲故事,接下来还是讲故事时间. 有人问我:你上大学的时候,有加入过学生会吗?读大学有没有必要加入学生会? 哎哟,这怎么回答呢,从短期来说,加入学生会有点用,至少可以娱乐一下,运气好的话,说不定能遇到红颜知己,但这概率相当低.从长远发展看嘛,是没什么用.老周当年读了四年本科,在学生会混了四年,什么名堂也没混出来. 一方面老周向来不求虚名,所以也没去参选所谓的什么部长.主席之类的,这些“官衔”听起来很高大上,实际上很

【Win 10 应用开发】UI Composition 札记(一):视图框架的实现

在开始今天的内容之前,老周先说一个问题,这个问题记得以前有人提过的. 设置 Windows.ApplicationModel.Core.CoreApplicationView.TitleBar.ExtendViewIntoTitleBar 属性可以让应用窗口中的内容扩展到标题栏.简单地说,就是你的UI区域可以扩大,并填充到标题栏,这在开发自定义标题栏或弄个什么毛玻璃效果时很有用. 不过,这个 ExtendViewIntoTitleBar 属性有个“八阿哥”,一旦你设置之后,系统会对其进行记录,很

【Win 10 应用开发】UI Composition 札记(三):与 XAML 集成

除了 DirectX 游戏开发,我们一般很少单独使用 UI Composition ,因此,与 XAML 互动并集成是必然结果.这样能够把两者的优势混合使用,让UI布局能够更灵活. 说到与 XAML 的集成,则我们必须先认识一位伙计,他非常重要,位于 Windows.UI.Xaml.Hosting 命名空间下,名叫 ElementCompositionPreview ,有了它,我们才可以在 XAML 元素与 Composition UI 元素之间游走.来看看它都公开了哪些成员. public s

【Win 10 应用开发】UI Composition 札记(四):绘制图形

使用 Win 2D 组件,就可以很轻松地绘制各种图形,哪怕你没有 D2D 相关基础,也不必写很复杂的 C++ 代码. 先来说说如何获取 Win 2D 组件.很简单,创建 UWP 应用项目后,你打开“解决方案资源管理器”窗口,然后在[引用]节点上右击,从快捷菜单中选择[管理 Nuget 程序包]命令,在打开的窗口中搜索“Win 2D”,然后安装带有 uwp 标识的那个就可以了. 顺便说一下,nuget 的包缓存在你的用户文件夹下,就是系统盘下的 \users\xxx,xxx是你登录系统的用户名,在

【Win 10 应用开发】UI Composition 札记(六):动画

动画在 XAML 中也有,而且基本上与 WPF 中的用法一样.不过,在 UWP 中,动画还有一种表现方式—— 通过 UI Composition 来创建. 基于 UI Composition 的动画,相对于 XAML 动画,有以下优点: 1.不使用 UI 线程,XAML 动画是共享 UI 线程的,而 Composition 中的动画是使用辅助线程的. 2.Composition 动画支持表达式(计算公式)来产生动画,相对灵活. 老周的建议是:两者都用,因为基于 XAML 和基于 Composit