天天看點

【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 的背景弄成黑色。子元素中有文字,也有二師兄的頭像,有關二師兄的照片,你可以網上找,二師兄那麼出名,網上有他的照片。

首先,讓大家看看,沒有應用燈光效果時,二師兄的身份證。

【Win 10 應用開發】UI Composition 劄記(八):用 XamlLight 制作燈光效果

二師兄還是那麼帥。然後,轉到代碼視圖,為 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>      

再次運作,就可以看到,隻有二師兄的豬頭上有燈光,而右邊的文本不會被燈光照亮。

【Win 10 應用開發】UI Composition 劄記(八):用 XamlLight 制作燈光效果

OK,今天的内容就扯到這裡,88。