前面老周已介紹過燈光的使用,如果你忘了,請用九牛二虎之力猛點選 這裡 去複習一下。本篇老周再介紹另一種添加燈光的方法,這種方法是專為 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。