[WPF]數據綁定時為何會出現StringFormat失效
在數據綁定過程中,我們經常會使用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 類型。

浙公網安備 33010602011771號