在 WPF 里面,可以通過 DrawingVisual 來進行使用底層的繪制方法,此方法需要呼叫 DrawingVisual 的 RenderOpen 拿到 DrawingContext 型別的物件,接著呼叫此物件的方法來進行界面繪制,在繪制完成之后,如果依然保存繪制程序的物件,例如 Transform 物件,那當界面再次重繪時,如果更改此物件的屬性,將會影響渲染
似乎這不是一個可以做簡單描述的問題,其實這個問題也讓我前天花了半天的時間才解決的一個界面渲染問題的其中一個,我在撰寫一個簡單的輕量的文本庫的時候,發現了文本字排版存在了一點問題,我的文本排版才能的是將文本轉換為 Geometry 物件,接著在 DrawingContext 里面繪制出來,我為了實作讓文本可以疊加特效的功能,因此不采用 GlyphRun 型別,同時為了減少 Geometry 物件的創建,我不能在 Geometry 物件上疊加變換
因為為了讓文本的字能排版對,我就需要設定每個字在界面繪制的坐標,為了簡化邏輯,我采用一個 RectangleGeometry 來代替文字的 Geometry 物件,如基礎的知識,在 DrawingContext 里面如果想要在指定的地方繪制某個內容,可以采用的方法是呼叫 PushTransform 方法,設定當前繪制的變換,也就包括了設定當前繪制在哪,如下面代碼
var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
var rectangleGeometry = new RectangleGeometry(new Rect(0, 0, 10, 10));
for (int i = 0; i < 10; i++)
{
var translateTransform = new TranslateTransform();
translateTransform.X = i * 15;
drawingContext.PushTransform(translateTransform);
drawingContext.DrawGeometry(Brushes.Red, null, rectangleGeometry);
drawingContext.Pop();
}
}
此時的界面能作業,大概如下

然而我看到了每次都需要創建一個 TranslateTransform 物件,我覺得也許會影響記憶體,是否 TranslateTransform 物件可以和 RectangleGeometry 物件一樣復用,在呼叫 Pop 方法之后,是否 TranslateTransform 物件的內容已被拷貝,于是我變更代碼如下
var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
var rectangleGeometry = new RectangleGeometry(new Rect(0, 0, 10, 10));
var translateTransform = new TranslateTransform();
for (int i = 0; i < 10; i++)
{
translateTransform.X = i * 15 + 10;
drawingContext.PushTransform(translateTransform);
drawingContext.DrawGeometry(Brushes.Red, null, rectangleGeometry);
drawingContext.Pop();
}
}
此時的 TranslateTransform 是復用的,然而界面就不能很好作業,所有的矩形都會繪制到最后的地方,看起來 PushTransform 內部沒有拷貝 TranslateTransform 的物件,只是記錄這條指令而已

從以上的例子可以看到在 DrawingContext 里面繪制的內容,其實呼叫 PushTransform 方法只是將傳入的 TranslateTransform 進行記錄,而沒有進行更多的拷貝,在后續變更 TranslateTransform 時,將會在渲染的時候,讀取到變更之后的 TranslateTransform 物件的屬性
在呼叫 DrawingVisual 的 RenderOpen 之后,在 DrawingContext 里面呼叫繪制方法時,不是立刻進行繪制,而是收集繪制的指令,實際的繪制渲染是在渲染執行緒通過 DirectX 等來實作的
在 RenderOpen 關閉之后,對 TranslateTransform 物件的變更也會影響到最終的渲染結果,因為 RenderOpen 關閉時不是立刻進行渲染,如下面代碼,將會讓所有的繪制的矩形都放在 X 是 500 的地方
var drawingVisual = new DrawingVisual();
var translateTransform = new TranslateTransform();
using (var drawingContext = drawingVisual.RenderOpen())
{
var rectangleGeometry = new RectangleGeometry(new Rect(0, 0, 10, 10));
for (int i = 0; i < 10; i++)
{
translateTransform.X = i * 15;
drawingContext.PushTransform(translateTransform);
drawingContext.DrawGeometry(Brushes.Red, null, rectangleGeometry);
drawingContext.Pop();
}
}
translateTransform.X = 500;
那如果再做一些更有趣的事情呢?我在不斷的更改 TranslateTransform 的屬性,如下面代碼
class Foo : UIElement
{
public Foo()
{
var drawingVisual = new DrawingVisual();
var translateTransform = new TranslateTransform();
using (var drawingContext = drawingVisual.RenderOpen())
{
var rectangleGeometry = new RectangleGeometry(new Rect(0, 0, 10, 10));
for (int i = 0; i < 10; i++)
{
translateTransform.X = i * 15;
drawingContext.PushTransform(translateTransform);
drawingContext.DrawGeometry(Brushes.Red, null, rectangleGeometry);
drawingContext.Pop();
}
}
translateTransform.X = 500;
Visual = drawingVisual;
SetTranslateTransform(translateTransform);
}
private async void SetTranslateTransform(TranslateTransform translateTransform)
{
while (true)
{
translateTransform.X++;
if (translateTransform.X > 700)
{
translateTransform.X = 0;
}
await Task.Delay(TimeSpan.FromMilliseconds(10));
}
}
protected override Visual GetVisualChild(int index) => Visual;
protected override int VisualChildrenCount => 1;
private Visual Visual { get; }
}
以上代碼的預期行為是什么?還請大家跑跑試試
其實就是界面在做影片,只是此影片有些有趣,需要在界面有其他邏輯進行界面重繪的時候,或者說觸發渲染執行緒進行渲染時,才會進行影片重繪
本文所有代碼放在 github 和 gitee 歡迎小伙伴訪問
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/294335.html
標籤:.NET技术
