一.ScrollViewer
在WPF自定義控件 —— 自繪篇我們做了 一個可拖動的矩形,但你是否發現當矩形拖出背景後就不見了,一般來說對於不 可見區域需要有ScrollBar來呈現,如圖:
對於這一應用在WPF中最常用的應該在控件外面包個ScrollViewer,那 麼如何使得我們的控件支持ScrollViewer呢?
首先我們來了解一下 ScrollViewer基本原理
通過上圖我們可以看到ScrollViewer是以Grid為容器組成的控件,其 中主要包括ScrollContentPresenter,和兩個ScrollBar,其中ScrollBar就是我 們第一張圖中看到那兩條,它也是一個由多個控件組成的復合控件,在這裡先略 過ScrollBar;來看紅色邊框內的ScrollContentPresenter,可以看到我們的控 件CustomerRender在ScrollContentPresenter內,那麼我們控件呈現的位置必定 是由它來控制的。
這個神秘的ScrollContentPresenter到底做了什麼能 讓我們看到一部分內容呢?看下這張圖就清楚了
我們可以知道ScrollContentPresenter實際對我們玩了一個遮罩效果 ,把我們的控件當作一個背景圖,用ScrollBar來移動背景位置,在 ScrollViewer外的控件可視部分統統被裁減掉了。只要繼承UIElement的控件就 可以重載GetLayoutClip方法來剪切區域。
protected override Geometry GetLayoutClip(Size layoutSlotSize)
{
return new RectangleGeometry(new Rect(base.RenderSize));
}
二.ArrangeOverride
ScrollContentPresenter又是如何 控制子元素的坐標呢?重載ArrangeOverride函數便可,具體看代碼注釋
protected override Size ArrangeOverride(Size arrangeBounds)
{
//得到集合中的第一個元素
UIElement visualChild = this.GetVisualChild(0) as UIElement;
//把子元素的左上角坐標定義到容器之外
Point point = new Point(-40, -50);
if (visualChild != null)
{
Rect finalRect = new Rect(point, visualChild.DesiredSize);
//設置元素坐標和大小
visualChild.Arrange(finalRect);
}
return arrangeBounds;
}
這段代碼中參數arrangeBounds 是父容器傳進的值,一般表示你可以有多大的利用空間,這個函數的返回值一般 指的是你控件RenderSize的大小.
RenderSize有什麼用?(歡迎大家補 充)
在onRender裡可以用,比如畫背景。
在MeasureOverride函數 中當參數值為無限大時用來得知可用空間的大小。
三.MeasureOverride
visualChild.DesiredSize的值實際就是我們常用的 ActualHeight和ActualWidth的源頭,也就是控件的實際大小,我們可以重載 MeasureOverride產生。下面是我們的自定義控件用的。
protected override Size MeasureOverride(Size constraint)
{
Size size = new Size
(
//判斷形參constraint中傳的值大,還是我們的Rectangle的值大, 以最大的那個作為控件的長寬
Math.Max(double.IsInfinity (constraint.Width) ? this.RenderSize.Width : constraint.Width, _preivewRectangle.Right),
Math.Max(double.IsInfinity (constraint.Height) ? this.RenderSize.Height : constraint.Height, _preivewRectangle.Bottom));
return size;
}
要說明下的是外容器的大小並不會觸發MeasureOverride(如把窗 體拖大),只會觸發ArrangeOverride,如果你要重新為DesiredSize賦值並通知父 容器請使用Measure函數,它會調用父控件的OnChildDesiredSizeChanged方法來 通知,同理父控件要監聽子控件的大小變化只要重載該方法即可,這個方法可以 一直沿著可視樹向上引發InvalidateMeasure函數,InvalidateMeasure通過 DispatcherPriority為Render來異步調用Measure。
ArrangeOverride中 盡量不要調用本身的Measure,Measure函數會再次調用InvalidateArrange方法 從而引起循環。控件容器放生變化時可以重載OnRenderSizeChanged實現。
Measure和Arrange的具體關系如下圖:
四.補充
如果想要自定義的ScrollBar你可以能要根絕ScrollBar的值 實時進行重繪圖,這個好處是數據量大,你只需呈現當前畫面中的圖形,缺點是 動一動就要重繪。通過例如ScrollViewer裁減的方式,遮罩得時候不會重繪,不 過剛開始呈現的時候數據量大會慢。
另外在復合控件中配合Transform中 的各種類來進行布局,使用CompositionTarget和動畫類可以產生很多效果。
本文配套源碼