日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

【W(wǎng)PF學(xué)習(xí)】第六十八章 自定義繪圖元素

 路人甲Java 2021-04-01

  上一章分析了WPF元素的內(nèi)部工作元素——允許每個(gè)元素插入到WPF布局系統(tǒng)的MeasureOverride()和ArrangeOverride()方法中。本章將進(jìn)一步深入分析和研究元素如何渲染自身。

  大多數(shù)WPF元素通過(guò)組合方式創(chuàng)建可視化外觀。換句話說(shuō),典型的元素通過(guò)其他更基礎(chǔ)的元素進(jìn)行構(gòu)建。例如,使用標(biāo)記定義用戶控件的組合元素,處理標(biāo)記的方式與自定義窗口中的XAML相同。使用控件模板為自定義控件定義可視化樹(shù)。并且當(dāng)創(chuàng)建自定義面板時(shí),根本不必定義任何可視化細(xì)節(jié)。組合元素由克難攻堅(jiān)使用者提供,并添加到Children集合?!?/p>

  當(dāng)然,知道現(xiàn)在才能使用組合。最終,一些類需要負(fù)責(zé)繪制內(nèi)容。在WPF中,這些類位于元素樹(shù)的底層。在典型窗口中,是通過(guò)單獨(dú)的文本、形狀以及位圖執(zhí)行渲染的,而不是通過(guò)高級(jí)元素。

一、OnRender()方法

  為了執(zhí)行自定義渲染,元素必須重寫OnRender()方法,該方法繼承自UIElement基類。OnRender()方法未必不需要替換組合——一些控件使用OnRender()方法繪制可視化細(xì)節(jié)并使用組合在其上疊加其他元素。Border和Panel類是兩個(gè)例子,Border類在OnRender()方法中繪制邊框,Panel類在OnRender()方法中繪制背景。Border和Panel類都支持子內(nèi)容,并且這些子內(nèi)容在自定義的繪圖細(xì)節(jié)之上進(jìn)行渲染。

  OnRender()方法接受一個(gè)DrawingContext對(duì)象,該對(duì)象為繪制內(nèi)容提供了了一套很有用的方法。在OnRender()方法中執(zhí)行繪圖的主要區(qū)別是不能顯示地創(chuàng)建和關(guān)閉DrawingContext對(duì)象。這是因?yàn)閹讉€(gè)不同的OnRender()方法可能使用相同的DrawingContext對(duì)象。例如,派生的元素可以執(zhí)行一些自定義繪圖操作并調(diào)用基類中的OnRender()方法來(lái)繪制其他內(nèi)容。這種方法是可行的,因?yàn)楫?dāng)開(kāi)始這一過(guò)程時(shí),WPF會(huì)自動(dòng)創(chuàng)建DrawingContext對(duì)象,并且當(dāng)不再需要時(shí)關(guān)閉對(duì)象。

  關(guān)于WPF渲染,最令人驚奇的細(xì)節(jié)是實(shí)際上只需要使用很少的類。大多數(shù)類是通過(guò)其他更簡(jiǎn)單的類構(gòu)建的,并且對(duì)于典型的控件,為了找到實(shí)際重寫OnRender()方法的類,需要進(jìn)入到控件元素樹(shù)中非常深的層次。下面是一些重寫OnRender()方法的類:

  •   TextBlock類。無(wú)論在何處放置文本,都有TextBlock對(duì)象使用OnRender()方法繪制文本。
  •   Image類。Image類重寫OnRender()方法,使用DrawingContext.DrawImage()方法繪制圖形內(nèi)容。
  •   MediaElement類。如果正在使用該類播放視頻文件,該類會(huì)重寫OnRender()方法以繪制視頻幀。
  •   各種形狀類。Shape基類重寫了OnRender()方法,通過(guò)使用DrawingContext.DrawGeometry()方法,繪制在其內(nèi)部存儲(chǔ)的Geometry對(duì)象。根據(jù)Shape類的特定派生類,Geometry對(duì)象可以表示橢圓、矩形或更復(fù)雜的由直線和曲線構(gòu)成的路徑。許多元素使用形狀繪制小的可視化細(xì)節(jié)。
  •   各種修飾類。這些類(如ButtonChrome和ListBoxChrome)繪制通用控件的外側(cè)外觀,并在具體制定的內(nèi)部放置內(nèi)容。其他許多繼承自Decorator的類,如Border類,都重寫了OnRender()方法。
  •   各種面板類。盡管面板的內(nèi)容是由其子元素提供的,但是OnRender()方法繪制具有背景色(假設(shè)設(shè)置了Background屬性)的矩形。

  通常,OnRender()方法的實(shí)現(xiàn)看起來(lái)很簡(jiǎn)單。例如,下面是繼承自Shape類的所有渲染代碼:

protected override void OnRender(DrawingContext drawingContext)
{
    this.EnsureRenderedGeometry();
    if(this._renderedGeometry!=Geometry.Empty)
    {
        drawingContext.DrawingGeometry(this.Fill,this.GetPen(),this._renderedGeometry);
    }
}

  請(qǐng)記住,重寫OnRender()方法不是渲染內(nèi)容并且將其添加到用戶界面的唯一方法。也可以創(chuàng)建DrawingVisual對(duì)象,并是喲AddVisualChild()方法為UIElement對(duì)象添加該可視化對(duì)象。然后可以調(diào)用DrawingVisual.RenderOpen()方法為DrawingVisual對(duì)象檢索DrawingContext對(duì)象,并使用返回的DrawingContext對(duì)象渲染DrawingVisual對(duì)象的內(nèi)容。

  在WPF中,一些元素使用這種策略在其他元素內(nèi)容之上顯示一些圖形細(xì)節(jié)。例如,在拖放指示器、錯(cuò)誤指示器以及焦點(diǎn)框中可以看到這種情況。在所有這些情況中,DrawingVisual類允許元素在其他內(nèi)容之上繪制內(nèi)容,而不是在其他內(nèi)容之下繪制內(nèi)容。但對(duì)于大部分情況,是在專門的OnRender()方法中進(jìn)行渲染。

二、評(píng)估自定義繪圖

  當(dāng)創(chuàng)建自定義元素時(shí),可能會(huì)選擇重寫OnRender()方法來(lái)繪制自定義內(nèi)容。可在包含內(nèi)容的元素(最常見(jiàn)的情況是繼承自Decorator的類)中重寫OnRender()方法,從而可以在內(nèi)容周圍添加圖形裝飾。也可以在沒(méi)有任何嵌套內(nèi)容的元素中重寫OnRender()方法,從而可以繪制元素的整個(gè)可視化外觀。例如,可以創(chuàng)建繪制一些小的圖形細(xì)節(jié)的自定義元素,然后可以通過(guò)組合,在其他類中使用自定義元素。WPF中的這方面示例是TickBar元素,該元素為Slider控件繪制刻度標(biāo)記。TickBar元素通過(guò)Slider控件的默認(rèn)控件模板(該模板還包括一個(gè)Border和一個(gè)Track元素,Track元素又包含了兩個(gè)RepeatButton控件和一個(gè)Thumb元素)嵌入到Slider控件的可視化樹(shù)中。

  一個(gè)明顯的問(wèn)題是需要確定何時(shí)使用較低級(jí)的OnRender()方法,以及何時(shí)使用其他類(l例如,繼承自Shape類的元素)的組合來(lái)繪制所需的內(nèi)容。為了做出決定,需要評(píng)估所需圖形的復(fù)雜程度以及希望提供的交互能力。

  例如,分析一下ButtonChrome類。在ButtonChrome類的WPF實(shí)現(xiàn)中,自定義的渲染代碼考慮了各種屬性,包括RenderDefaulted、RenderMouseOver以及RenderPressed。Button類的默認(rèn)控件模板在適當(dāng)?shù)臅r(shí)機(jī)使用觸發(fā)器設(shè)置這些屬性。例如,當(dāng)將鼠標(biāo)移動(dòng)到按鈕上時(shí),Button類使用觸發(fā)器將ButtonChrome.RenderMouseOver屬性設(shè)置為true。

  無(wú)論何時(shí)改變RenderDefaulted、RenderMouseOver或RenderPressed屬性,ButtonChrome類都會(huì)調(diào)用基本的InvalidateVisual()方法來(lái)指示當(dāng)前外觀不在有效。WPF然后調(diào)用ButtonChrome.OnRender()方法來(lái)獲取新的圖形表示。

  如果ButtonChrome類使用組合,這種行為就更難實(shí)現(xiàn)。使用合適的元素為ButtonChrome類創(chuàng)建標(biāo)準(zhǔn)外觀很容易,但是當(dāng)按鈕的狀態(tài)發(fā)生變化是,需要做更多的工作來(lái)修改外觀。需要?jiǎng)討B(tài)改變構(gòu)成ButtonChrome類的嵌套元素,如果外觀變化很大的話,就必須隱藏一個(gè)元素并在合適的位置顯示另一個(gè)元素。

  大多數(shù)自定義元素不需要自定義渲染。但是當(dāng)屬性發(fā)生變化或執(zhí)行特定操作是,需要渲染復(fù)雜的變化很大的可視化外觀,此時(shí)使用自定義的渲染方法可能更加簡(jiǎn)單并且更便捷。

三、自定義繪圖元素

  通過(guò)前面對(duì)OnRender()方法的介紹,理解其工作原理。下面使用OnRender()方法創(chuàng)建自定義控件。

  下面創(chuàng)建了一個(gè)名為CustomDrawnElement的元素,演示了一種簡(jiǎn)單的效果。該元素使用RadialGradientBrush畫刷繪制陰影背景,技巧是動(dòng)態(tài)設(shè)置強(qiáng)調(diào)顯示的漸變起點(diǎn),使用其跟隨鼠標(biāo)。從而當(dāng)用戶在控件上移動(dòng)鼠標(biāo)時(shí),白色的發(fā)光中心點(diǎn)跟隨鼠標(biāo)移動(dòng)。

  CustomDrawnElement元素不需要包含任何子內(nèi)容,所以它直接繼承自FrameworkElement類。該元素只提供了一個(gè)可以設(shè)置的屬性——漸變的背景色。

public class CustomDrawnElement:FrameworkElement
    {
        public static DependencyProperty BackgroundColorProperty;

        static CustomDrawnElement()
        {
            FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata(Colors.Yellow);
            metadata.AffectsRender = true;
            BackgroundColorProperty = DependencyProperty.Register("BackgroundColor",
                typeof(Color), typeof(CustomDrawnElement), metadata);
        }


        public Color BackgroundColor
        {
            get
            {
                return (Color)GetValue(BackgroundColorProperty);
            }
            set
            {
                SetValue(BackgroundColorProperty, value);
            }
        }
        ...
}

  BackgroundColor依賴性屬性使用FrameworkPropertyMetadata.AffectRender標(biāo)志明確進(jìn)行了標(biāo)識(shí)。因此,無(wú)論何時(shí)改變了背景色,WPF都自動(dòng)調(diào)用OnRender()方法。然而,當(dāng)鼠標(biāo)移動(dòng)到新的位置時(shí),也需要確保調(diào)用OnRender()方法。這是通過(guò)在合適的時(shí)間調(diào)用InvalidateVisual()方法實(shí)現(xiàn)的。

        . . .
        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            this.InvalidateVisual();
        }

        protected override void OnMouseLeave(MouseEventArgs e)
        {
            base.OnMouseLeave(e);
            this.InvalidateVisual();
        }
       . . .

  剩下的唯一細(xì)節(jié)是渲染代碼。渲染代碼使用DrawingContext.DrawRectangle()方法繪制元素的背景。ActualWidth和ActualHeight屬性只是控件最終的渲染尺寸。

        . . .
        protected override void OnRender(DrawingContext dc)
        {
            base.OnRender(dc);

            Rect bounds = new Rect(0, 0, base.ActualWidth, base.ActualHeight);
            dc.DrawRectangle(GetForegroundBrush(), null, bounds);
        }
        . . .

  最后,名為GetForegroundBrush()的私有輔助方法根據(jù)鼠標(biāo)的當(dāng)前位置構(gòu)造正確的RadialGradientBrush畫刷。為了計(jì)算中心點(diǎn),需要將鼠標(biāo)在元素上懸停的當(dāng)前位置轉(zhuǎn)換成從0到1的相對(duì)位置,這正是RadialGradientBrush畫刷期望的結(jié)果。

        . . .
        private Brush GetForegroundBrush()
        {
            if (!IsMouseOver)
            {
                return new SolidColorBrush(BackgroundColor);
            }
            else
            {
                RadialGradientBrush brush = new RadialGradientBrush(Colors.White, BackgroundColor);
                Point absoluteGradientOrigin = Mouse.GetPosition(this);
                Point relativeGradientOrigin = new Point(
                    absoluteGradientOrigin.X / base.ActualWidth, absoluteGradientOrigin.Y / base.ActualHeight);

                brush.GradientOrigin = relativeGradientOrigin;
                brush.Center = relativeGradientOrigin;
                brush.Freeze();
                return brush;
            }
        }
        . . .

四、創(chuàng)建自定義裝飾元素

  作為一條通用規(guī)則,切勿在控件中使用自定義繪圖。如果在控件中使用自定義繪圖,就違反了WPF無(wú)外觀空間的承諾。問(wèn)題是一旦硬編碼一些繪圖邏輯,就會(huì)使控件可視化外觀的一部分不能通過(guò)控件模板進(jìn)行定制。更好的方法是設(shè)計(jì)單獨(dú)的繪制自定義內(nèi)容的元素(如上面示例中的CustomDrawnElement類),然后在控件的默認(rèn)模板內(nèi)部使用自定義元素。

  有必要快速分析一下如何修改上面示例,使其能夠成為控件模板的一部分。在控件模板中,自定義繪圖元素通常扮演兩個(gè)角色:

  •   它們繪制一些小的圖形細(xì)節(jié)(例如滾動(dòng)按鈕上的箭頭)。
  •   它們?cè)诹硪粋€(gè)元素的周圍提供更詳細(xì)的背景或邊框。

  第二種方法需要自定義裝飾元素,可以通過(guò)兩個(gè)輕微的改動(dòng)將CustomDrawnElement類轉(zhuǎn)換成自定義繪圖元素。首先,使該類繼承自Decorator類:

public class CustomDrawnDecorator:Decorator

  然后重寫OnMeasure()方法,指定需要的尺寸,所有裝飾元素都會(huì)考慮它們的子元素,增加裝飾所需要的額外空間,然后返回組合之后的尺寸。CustomDrawnDecorator類不需要任何額外的空間來(lái)繪制邊框,相反,使用下面的代碼簡(jiǎn)單地使其自定和其內(nèi)容具有相同的尺寸:

protected override Size MeasureOverride(Size constraint)
        {
            UIElement child = this.Child;
            if (child != null)
            {
                child.Measure(constraint);
                return child.DesiredSize;
            }
            else
            {
                return new Size();
            }

        }

  一旦創(chuàng)建自定義裝飾元素,就可以在自定義控件模板中使用它們。例如,下面的按鈕模板在按鈕內(nèi)容的后面放置了跟隨鼠標(biāo)蹤跡的漸變背景。使用模板綁定確保使用對(duì)齊屬性和內(nèi)邊距屬性。

<ControlTemplate x:Key="ButtonWithCustomChrome">
            <lib:CustomDrawnDecorator BackgroundColor="LightGreen">
                <ContentPresenter Margin="{TemplateBinding Padding}"
         HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
         VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
         ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"
         Content="{TemplateBinding ContentControl.Content}"
         RecognizesAccessKey="True" />
            </lib:CustomDrawnDecorator>
        </ControlTemplate>

  現(xiàn)在可以使用這個(gè)模板重新樣式化按鈕,使其具有新的外觀。當(dāng)然,為了使自定義裝飾元素更加實(shí)用,當(dāng)單擊鼠標(biāo)按鈕時(shí)可能更希望改變它的外觀。使用修改裝飾類屬性的觸發(fā)器可以完成該工作。

  本章示例源碼:CustomDrawnElement.zip

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多