前言 Seal-Report 是一款.NET 開源報表工具,擁有 1.4K Star。它提供了一個完整的框架,使用 C# 編寫,最新的版本採用的是 .NET 8.0 。 它能夠高效地從各種資料庫或 NoSQL 數據源生成日常報表,並支持執行複雜的報表任務。 其簡單易用的安裝過程和直觀的設計界面,我們 ...
在數據綁定過程中,我們經常會使用StringFormat
對要顯示的數據進行格式化,以便獲得更為直觀的展示效果,但在某些情況下格式化操作並未生效,例如 Button
的 Content
屬性以及ToolTip
屬性綁定數據進行StringFormat
時是無效的。首先回顧一下StringFormat
的基本用法。
StringFormat
的用法
StringFormat
是 BindingBase
的屬性,指定如果綁定值顯示為字元串,應如何設置該綁定的格式。因此,BindingBase
的三個子類:Binding
、MultiBinding
、PriorityBinding
都可以對綁定數據進行格式化。
Binding
Binding
是最常用的綁定方式,使用StringFormat
遵循.Net格式字元串標準即可。例如:
<TextBlock Text="{Binding Price,ElementName=self,StringFormat={}{0:C}}"/>
或者
<TextBlock Text="{Binding TestString,ElementName=self,StringFormat=test:{0}}"/>
其中{0}
表示第一個數值,如果 StringFormat
屬性的值是以花括弧開頭,前邊需要有一對花括弧 {}
進行轉義,也就是第一個例子中的 {}{0:C}
,否則不需要,如第二個示例一樣。
如果設置 Converter
和 StringFormat
屬性,則首先將轉換器應用於數據值,然後StringFormat
應用該值。
MultiBinding
Binding
綁定時,格式化只能指定一個參數,MultiBinding
綁定時則可指定多個參數。例如:
<TextBlock>
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} {1}">
<Binding Path="FirstName" ElementName="self"/>
<Binding Path="LastName" ElementName="self"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
這個例子中 MultiBinding
是由多個子 Binding
組成,StringFormat
僅在設置 MultiBinding
時適用,子 Binding
中雖然也可以設置 StringFormat
,但是會被忽略。
PriorityBinding
相比於前兩種綁定,PriorityBinding
使用的頻率沒那麼高,它的主要作用是按照一定優先順序順序設置綁定列表, 如果最高優先順序綁定在處理時成功返回值,則無需處理列表中的其他綁定。 如果計算優先順序最高的綁定需要很長時間,那麼將會使用成功返回值的次高優先順序,直到優先順序較高的綁定成功返回值。PriorityBinding
和其包含的綁定列表中的子 Binding
也都可以設置 StringFormat
屬性。例如:
<TextBlock
Width="100"
HorizontalAlignment="Center"
Background="Honeydew">
<TextBlock.Text>
<PriorityBinding FallbackValue="defaultvalue" StringFormat="haha:{0}">
<Binding IsAsync="True" Path="SlowestDP" StringFormat="hi:{0}"/>
<Binding IsAsync="True" Path="SlowerDP" />
<Binding Path="FastDP" />
</PriorityBinding>
</TextBlock.Text>
</TextBlock>
與 MultiBinding
不同的是,PriorityBinding
的子 Binding
中的 StringFormat
是會生效的,其規則是優先使用子 Binding
設置的格式,其次才使用PriorityBinding
設置的格式。
Content屬性格式化失效的原因
Button
的 Content
屬性可以用字元串賦值並顯示在按鈕上,但是使用 StringFormat
格式化並不會生效。原本我以為是涉及到類型轉換器,在類型轉換過程中處理掉了,但這隻是猜測,通過源碼發現並不是這樣的。在 BindingExpressionBase
中有這樣一段代碼:
internal virtual bool AttachOverride(DependencyObject target, DependencyProperty dp)
{
_targetElement = new WeakReference(target);
_targetProperty = dp;
DataBindEngine currentDataBindEngine = DataBindEngine.CurrentDataBindEngine;
if (currentDataBindEngine == null || currentDataBindEngine.IsShutDown)
{
return false;
}
_engine = currentDataBindEngine;
DetermineEffectiveStringFormat();
DetermineEffectiveTargetNullValue();
DetermineEffectiveUpdateBehavior();
DetermineEffectiveValidatesOnNotifyDataErrors();
if (dp == TextBox.TextProperty && IsReflective && !IsInBindingExpressionCollection && target is TextBoxBase textBoxBase)
{
textBoxBase.PreviewTextInput += OnPreviewTextInput;
}
if (TraceData.IsExtendedTraceEnabled(this, TraceDataLevel.Attach))
{
TraceData.TraceAndNotifyWithNoParameters(TraceEventType.Warning, TraceData.AttachExpression(TraceData.Identify(this), target.GetType().FullName, dp.Name, AvTrace.GetHashCodeHelper(target)), this);
}
return true;
}
其中第11行調用了一個名為 DetermineEffectiveStringFormat
的方法,顧名思義就是檢測有效的 StringFormat
。接下來看看裡邊的邏輯:
internal void DetermineEffectiveStringFormat()
{
Type type = TargetProperty.PropertyType;
if (type != typeof(string))
{
return;
}
string stringFormat = ParentBindingBase.StringFormat;
for (BindingExpressionBase parentBindingExpressionBase = ParentBindingExpressionBase; parentBindingExpressionBase != null; parentBindingExpressionBase = parentBindingExpressionBase.ParentBindingExpressionBase)
{
if (parentBindingExpressionBase is MultiBindingExpression)
{
type = typeof(object);
break;
}
if (stringFormat == null && parentBindingExpressionBase is PriorityBindingExpression)
{
stringFormat = parentBindingExpressionBase.ParentBindingBase.StringFormat;
}
}
if (type == typeof(string) && !string.IsNullOrEmpty(stringFormat))
{
SetValue(Feature.EffectiveStringFormat, Helper.GetEffectiveStringFormat(stringFormat), null);
}
}
這段代碼的作用就是檢測有效的 StringFormat
,並通過 SetValue
方法保存起來,從第4~7行代碼可以看到,一開始就會檢測目標屬性的類型是不是 String
類型,不是的話直接返回,綁定表達式中的 StringFormat
也就不會保存了。在後續的 BindingExpression
類計算綁定表達式值時獲取到 StringFormat
為 null
,也就不會進行格式化了。
Button
的 Content
屬性雖然可以用字元串賦值,但它其實的 Object
類型。因此,在檢測有效的 StringFormat
表達式時直接過濾了。ToolTip
也同樣是 Object
類型。
解決方法
對於 Content
這種 Object
類型的屬性綁定字元串並且需要格式化時,可以採用以下三種方式解決:
- 最通用的方法就是自定義
ValueConverter
,在ValueConverter
中對字元串進行格式化; - 綁定到其他可進行
StringFormat
的屬性上,比如TextBlock
的Text
屬性進行格式化,ToolTip
綁定到Text
上; - 既然是
Object
類型,那也可把TextBlock
作為Content
的值。
<Button Width="120" Height="30">
<Button.Content>
<TextBlock Text="{Binding TestString,ElementName=self,StringFormat=test:{0}}"/>
</Button.Content>
</Button>
小結
數據綁定時出現StringFormat失效的主要分為兩種情況。一是沒有遵循綁定時StringFormat使用的約束,二是綁定的目標屬性不是 String
類型。