[UWP]瞭解IValueConverter

来源:https://www.cnblogs.com/dino623/archive/2018/01/02/IValueConverter.html
-Advertisement-
Play Games

1. 前言 IValueConverter是用於數據綁定的強大的武器,它用於Value在Binding Source和Binding Target之間的轉換。本文將介紹IValueConverter的用法及一些常用的實現。 2. 為什麼要使用IValueConverter 假設有如下的類TestRe ...


1. 前言

IValueConverter是用於數據綁定的強大的武器,它用於Value在Binding Source和Binding Target之間的轉換。本文將介紹IValueConverter的用法及一些常用的實現。

2. 為什麼要使用IValueConverter

假設有如下的類TestResult:

public class TestResult
{
    public bool Passed { get; set; }

}

UI需要通過Passed這個屬性決定顯示結果的文字顏色為紅色或綠色,一般初學者最常見的做法是修改TestResult類,添加一個和Passed相關的屬性:

public class TestResult
{
    public bool Passed { get; set; }

    public Brush TestResultBrush
    {
        get
        {

            if (Passed)
                return new SolidColorBrush(Colors.Red);
            else
                return new SolidColorBrush(Colors.Green);
        }
    }
}

然後在XAML上綁定到這個屬性:

<TextBlock  Text="Score : 60" Foreground="{Binding TestResultBrush}"/>

另一種做法是直接才Code Behind為TextBlock更改Foreground:

var testResult = DataContext as TestResult;
if (testResult != null)
{
    if (testResult.Passed)
        ResultElement.Foreground = new SolidColorBrush(Colors.Red);
    else
        ResultElement.Foreground = new SolidColorBrush(Colors.Green);
}

兩種做法都不夠優雅,可以指出一大堆問題:破壞了TestResult的結構,違反了開放封閉原則,令UI和數據太過耦合,太多Hard Code等。

這種情況通常都可以使用IValueConverter處理。在Binding中,IValueConverter可以用於數據呈現前將它轉換成新的目標值,實現IValueConverter需要執行以下步驟:

  1. 創建一個實現了IValueConverter介面的類類;
  2. 實現public object Convert(object value, Type targetType, object parameter, string language)方法,該方法將數據轉換為新目標值;
  3. 實現public object ConvertBack(object value, Type targetType, object parameter, string language),該方法執行反向轉換,只有使用雙向綁定才需要實現這個方法。

在這個例子里,IValueConverter的目的是將bool類型的Passed轉換成Brush,實現如下:

public class BoolToBrushConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        if (value is bool passed)
            return new SolidColorBrush(passed ? Colors.Green : Colors.Red);

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

在XAML中使用這個Convnerter需要先將它定義為Resource,然後Binding中指定Converter到這個已定義的Resource:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.Resources>
        <local:BoolToBrushConverter x:Key="BoolToBrushConverter"/>
    </Grid.Resources>
    <TextBlock  Text="Score : 60" Foreground="{Binding Passed,Converter={StaticResource BoolToBrushConverter}}"/>
</Grid>

3. BoolToValueConverter

在XAML漫長的歷史里,IValueConverter也誕生了各種奇怪的技巧,其中最常用的是BoolToValueConverter。

public class BoolToValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        if (value == null || (bool) value == false)
            return DependencyProperty.UnsetValue;

        return parameter;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        return Equals(value, parameter);
    }
}

BoolToValueConverter靈活使用了Binding中ConverterParameterFallbackValue兩個參數,常常用於解決IValueConverter中HardCode的問題。在Binding中,FallbackValue指明瞭如果Binding沒法返回任何值時使用的值,在IValueConverter中返回DependencyProperty.UnsetValue即告訴Binding要使用FallbackValue的值。

使用BoolToValueConverter解決了上述例子的Hard Code的問題,在XAML中使用如下:

<Grid>
    <Grid.Resources>
        <local:BoolToValueConverter x:Key="BoolToValueConverter"/>
    </Grid.Resources>
    <TextBlock Text="Score : 60" Foreground="{Binding Passed,Converter={StaticResource BoolToValueConverter},ConverterParameter=Green,FallbackValue=Red}"/>
</Grid>

4. BoolToObjectConverter

需要註意的是上面XAML中Green和Red都只是字元串,它們最終能被解析成SolidColorBrush是由於TypeConveter的支持,也就是說上述XAML語法只能用於TypeConverter支持的數據類型,而且這種寫法還是太過HardCode。如果要支持複雜類型或者對應本地化等問題,可以將ConverterParameter和FallbackValue綁定到StaticResource :

<Grid.Resources>
    <SolidColorBrush Color="Green" x:Key="PassedBrush"/>
    <SolidColorBrush Color="Red" x:Key="FailedBrush"/>
    <local:BoolToValueConverter x:Key="BoolToValueConverter"/>
</Grid.Resources>
<TextBlock Text="Score : 60" Foreground="{Binding Passed,Converter={StaticResource BoolToValueConverter},ConverterParameter={StaticResource PassedBrush},FallbackValue={StaticResource FailedBrush}}"/>

雖然看上去是很靈活,但如果有大量返回同樣值的BoolToValueConverter將會使XAML產生大量冗餘。UWP Community Toolkit提供了一些常用的IValueConverter實現,其中最常用的是BoolToObjectConverter。BoolToObjectConverter和BoolToValueConverter功能類似,但它提供了public object TrueValue { get; set; }public object FalseValue { get; set; }兩個屬性,而且這兩個屬性是依賴屬性,可以使用綁定為其賦值。使用如下:

<Grid.Resources>
    <SolidColorBrush Color="Green" x:Key="PassedBrush"/>
    <SolidColorBrush Color="Red" x:Key="FailedBrush"/>
    <converters:BoolToObjectConverter x:Key="BoolToObjectConverter" TrueValue="{StaticResource PassedBrush}" FalseValue="{StaticResource FailedBrush}"/>
</Grid.Resources>
<TextBlock Text="Score : 60" Foreground="{Binding Passed,Converter={StaticResource BoolToObjectConverter}}"/>

5. BoolToVisibilityConverter

UWP Community Toolkit中提供了另一個常用的Converter:BoolToVisibilityConverter。這個Converter只是簡單地繼承了BoolToObjectConverter,並且為TrueValue和FalseValue設置了預設值:

public BoolToVisibilityConverter()
{
    TrueValue = Visibility.Visible;
    FalseValue = Visibility.Collapsed;
}

BoolToVisibilityConverter雖然簡單,但確實好用。不過從1607以後就不需要這個Converter了,微軟是這樣說的:

從 Windows 10 版本 1607 開始,XAML 框架向 Visibility 轉換器提供內置布爾值。 轉換器將 true 映射到 Visible 枚舉值並將 false 映射到 Collapsed,以便你可以將 Visibility 屬性綁定到布爾值,而無需創建轉換器。 若要使用內置轉換器,你的應用的最低目標 SDK 版本必須為 14393 或更高版本。

但有時候反而需要True對應Collapsed,於是現在是另一個常用Converter - BoolNegationConverter登上歷史舞臺的時候了:

<StackPanel >
    <StackPanel.Resources>
        <converters:BoolNegationConverter x:Key="BoolNegationConverter" />
    </StackPanel.Resources>
    <TextBlock Text="Passed" Foreground="Green" Visibility="{Binding Passed}"/>
    <TextBlock Text="Failed" Foreground="Red" Visibility="{Binding Passed,Converter={StaticResource BoolNegationConverter}}"/>
</StackPanel>

6. StringFormatConverter

UWP的Binding缺少了StringFormat,這對Binding產生了很大影響,為彌補這個缺陷,可以使用UWP Community Toolkit中的StringFormatConverter。它的代碼也十分簡單(其實這才是ConverterParameter的正確用法):

public object Convert(object value, Type targetType, object parameter, string language)
{
    if (value == null)
    {
        return null;
    }

    if (parameter == null)
    {
        return value;
    }

    return string.Format((string)parameter, value);
}

在XAML中使用如下:

<TextBlock Text="{Binding DoubleValue,Converter={StaticResource StringFormatConverter},ConverterParameter='{}{0:N2}'}"/>
<TextBlock Text="{Binding DoubleValue,Converter={StaticResource StringFormatConverter},ConverterParameter='There are {0:N0} Items'}"/>

結果如下:

除了彌補StringFormat的功能,StringFormatConverter還有其它的應用場景。

** TestModel.CS **

public IEnumerable<ClickMode> ClickModes => new List<ClickMode> { ClickMode.Hover, ClickMode.Press, ClickMode.Release };
<ListBox ItemsSource="{Binding ClickModes}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding }" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
<ListBox ItemsSource="{Binding ClickModes}"/>

在WPF中,以上XAML都可以正常呈現,而在UWP中,以上XAML顯示如下:

這種情況可以使用StringFormatConverter顯示枚舉的名稱:

<ListBox ItemsSource="{Binding ClickModes}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Converter={StaticResource StringFormatConverter}}" />
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

可以說對UWP來說StringFormatConverter十分必要。

7. language參數

public object Convert(object value, Type targetType, object parameter, string language)方法中的參數language通常用於本地化,例如可以創建一個DateTimeValueConverter:

public class DateTimeValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        if (value is DateTime dateTime)
        {
            var culture = new CultureInfo(language);
            return dateTime.ToString(culture.DateTimeFormat);
        }
        return value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}
<TextBlock Text="{Binding Date,Converter={StaticResource DateTimeValueConverter},ConverterLanguage=en-US}"/>
<TextBlock Text="{Binding Date,Converter={StaticResource DateTimeValueConverter},ConverterLanguage=zh-CN}"/>

結果如下:

8. targetType參數

targetType參數指轉換後的目標類型,使用這個參數可以實現一個簡單的Value Converter:

public class ValueConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        return System.Convert.ChangeType(value, targetType);
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

雖然代碼簡單,但它可以解決不少問題,例如 瞭解TypeConverter 這篇文章里提到的不能在XAML中使用decimal的問題。IValueConverter要起作用依賴於BindingSource,而在XAML中雖然很多東西都可以用來做BindingSource,例如用元素自己的Tag:

<local:MyContentControl Tag="10.01" Amount="{Binding Converter={StaticResource ValueConverter},Path=Tag,RelativeSource={RelativeSource Mode=Self}}"/>

或者Resources中的字元串:

<Grid.Resources>
    <x:String x:Key="DecimalString">10.01</x:String>
</Grid.Resources>
<local:MyContentControl Amount="{Binding Source={StaticResource DecimalString},Converter={StaticResource ValueConverter}}"/>

或者更進一步寫一個字元串的包裝類:

public class StringWrapper
{
    public string this[string key]
    {
        get
        {
            return key;
        }
    }
}
<local:MyContentControl Amount="{Binding [10.01],Source={StaticResource StringWrapper},Converter={StaticResource ValueConverter}}"/>

9. 使用IValueConverter的其它經驗

9.1 統一管理IValueConverter

由於大部分IValueConverter行為是固定的,通常我都會把常用的IValueConverter放到一個Converters.xaml,然後在App.xaml中年合併資源字典,這樣不用重覆寫創建Converter的xaml,也避免了重覆創建Converter的資源消耗:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Converters.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

9.2 格式化

Binding最讓人詬病的缺點就是它的語法太長太長太長,例如以上兩個TextBlock,在IDE中很難判斷這它們有什麼不同。很多時候我都會把XAML的格式化設置成“將每個屬性分行放置”,如下圖:

這樣上面兩個TextBlock的XAML就清晰許多了:

不過這樣設置也並不全是好處,怎麼設置具體還是看個人喜好和屏幕尺寸。

10. 結語

雖然IValueConverter的文章已經不少了,但還是常常見到亂來的IValueConverter實現,而且UWP的IValueConverter有一些改變,所以還是寫了這篇文章。

我很想寫一些常用的,或者容易用錯的基礎知識,但連IValueConverter都不知不覺就寫得這麼長了,實在沒勇氣寫Binding的概念,何況關於Binding 已經有很多很實用的文章。

我十分清楚文章寫得太長就會被“保存到Pocket”,我也想每篇文章都能在三五分鐘內看完,但偏偏越基礎的概念就越能寫得長,而且寫得簡短些又會被移出博客園首頁,很難把握尺度。

下一篇文章會儘量寫短一些。

11. 參考

IValueConverter Interface
Binding Class
深入瞭解數據綁定
Converters - UWP Community Toolkit _ Microsoft Docs


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 最近因為業務需求開發了一個介面用於接收數據,但是總有一些數據報出ORA-01704:字元串文字太長錯誤。仔細排查後發現,竟然是NCLOB類型欄位提示這個錯誤。NCLOB存儲空間有4G,怎麼也想不明白為什麼會報這個錯誤。原來因為介面插入數據採用字元串拼接的方式。 而oracle中會把字元串轉為varc ...
  • 1.定義一個枚舉,樂器類型 #region 樂器定義 public enum MusicNo:uint { /// <summary> /// //大鋼琴 /// </summary> AcousticGrandPiano = 0, /// <summary> ///明亮的鋼琴 /// </summ ...
  • 一、新建項目 打開vs2017,新建一個項目,命名為orm1 二、新建資料庫 打開 SqlServer資料庫,新建資料庫 orm1,並新建表 student 。 三、新建 ADO.NET 實體數據模型 這裡點擊 新建連接,新建資料庫連接。其實伺服器名輸入 . 代表本地伺服器,身份驗證選擇預設的Win ...
  • 寫在前面 整個項目都托管在了 Github 上:https://github.com/ikesnowy/Algorithms-4th-Edition-in-Csharp 這一節內容可能會用到的庫文件有 Sort和 SortData,同樣在 Github 上可以找到。 善用 Ctrl + F 查找題目 ...
  • 一、前言 時間過得真是快,轉眼就2018年了。首先祝各位博友,軟體開發者新年新氣象,事業有成,身體健康,闔家幸福!最近看到園子里好多關於自己的2017年度總結以及對自己新一年的願景,覺得咱園子的氛圍是真的好。這三天假期我也沒閑著,一邊看OB海鮮團吃雞一邊寫Socket SocketAsyncEven ...
  • 並行演算法有可能非常複雜,並且或多或少涵蓋了這些並行集合。線程安全並不是沒有代價的。比起System.Collections和System.Collections.Generic命名空間中的經典列表 、集合和數組來說,併發集合會有更大的開銷,因此,應該只在需要從多個任務中併發訪問集合的時候才使用併發集... ...
  • 繼承,多態,封裝 在C#中,為了能夠合理描述自然界的規律,面向對象的編程引入了繼承的概念,是面向對象編程中最重要的概念之一,定義瞭如何根據現有的類創建新類的過程. 繼承:一個類派生出來的子類具有這個類所有的公共屬性和方法。 父類派生子類,子類繼承父類。 語法-- 子類:父類 子類和父類 被繼承的類稱 ...
  • .net連接操作SqlServer資料庫及最基本的增刪改查。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...