WPF進階技巧和實戰08-依賴屬性與綁定03

數據提供者

在大多數的程式碼中,都是通過設置元素的DataContext屬性或者列表控制項的ItemsSource屬性,從而提供頂級的數據源。當數據對象是通過另一個類構造時,可以有其他選擇。

一種是作為窗口的資源定義數據對象。如果能夠使用聲明的方式構造對象,這種方法工作的很好,但是如果需要在運行時使用資料庫等方式獲取數據,這種技術就沒有意義了。但是會出現部分開發人員採用這種方法,基本思路是在構造函數中獲取所需的數據。

<Window.Resources>
    <local:PhotoList x:Key="MyPhotos" />
</Window.Resources>

public class Photo
{
    public Photo(string path, string eyeFlag)
        {
            _source = path;

            if (!File.Exists(path)) myBmp = null;
            try
            {
                myBmp = new BitmapImage();
                myBmp.BeginInit();
                myBmp.CacheOption = BitmapCacheOption.OnLoad;
                myBmp.StreamSource = new MemoryStream(File.ReadAllBytes(path));
                myBmp.EndInit();
                myBmp.Freeze();
            }
            catch (Exception)
            {
                myBmp = null;
            }

            EyeFlag = eyeFlag;
        }

    public override string ToString()
        {
            return Source;
        }

    private string _source;
    public string Source { get { return _source; } }

    private BitmapImage myBmp;
    public BitmapImage MyBmp
        {
            get { return myBmp; }
            set { myBmp = value; }
        }

    private string _EyeFlag;
    public string EyeFlag
        {
            get { return _EyeFlag; }
            set
            {
                _EyeFlag = value;
                //this.OnPropertyChanged("EyeFlag");
            }
        }
}

public class PhotoList : ObservableCollection<Photo>
{
    public void FilePathAdd(string bmpFile, string eyeFlag = "")
    {
        Add(new Photo(bmpFile, ""));
    }
}

此處的PhotoList類繼承自ObservableCollection類,因此他能夠存儲列表,可以通過方法或者直接在構造函數中填充數據。現在其他元素就可以在他們的綁定中使用這個資源了。

<ListBox ItemsSource="{Binding Source={StaticResource MyPhotos}}">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>

這種方法看起來可以,但是有一定風險。當添加錯誤處理時,需要將錯誤處理程式碼放在PhotoList類中。這種方式將數據模型、數據訪問程式碼以及用戶介面程式碼混合在一起,所以當需要訪問外部資源時,這種方法就很不合理了。
數據提供者是這種模型的擴展,可以通過數據提供者直接綁定帶在標記的資源部分定義的對象,然而,不是直接綁定到數據對象自身,而是綁定到能夠檢索或者構建數據對象的數據提供者。如果數據提供者能夠在發生異常時引發事件並提供用於配置與其操作相關細節的屬性,這種方法就合理。但是WPF的數據提供者還沒有達到這個標準,導致不值得使用這種方法。WPF提供了兩種數據提供者:

  • ObjectDataProvider,數據提供者通過調用另一個類中的方法獲取資訊
  • XmlDataProvider,直接從XML文件獲取資訊

ObjectDataProvider

ObjectDataProvider能夠從應用程式的另一個類中獲取資訊。

  • 能夠創建需要的對象並為構造函數傳遞參數
  • 能夠調用所創建對象中的方法,並向他傳遞方法參數
  • 能夠非同步創建數據對象(能夠窗口載入之前一直等待,之後在後台完成工作)
<Window.Resources>
    <ObjectDataProvider x:Key="MyPhotosProvider" MethodName="GetAll" ObjectType="{x:Type local:PhotoList}">
        <ObjectDataProvider.MethodParameters />
    </ObjectDataProvider>
    <local:PhotoList x:Key="MyPhotos" />
</Window.Resources>
<Grid>
    <ListBox ItemsSource="{Binding Source={StaticResource MyPhotos}}">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <VirtualizingStackPanel Orientation="Horizontal" />
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
    </ListBox>
    <ListBox ItemsSource="{Binding Source={StaticResource MyPhotosProvider}}">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <VirtualizingStackPanel Orientation="Horizontal" />
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
    </ListBox>
</Grid>

  1. 錯誤提示

當創建窗口時,XAML創建窗口並調用GetAll()方法,從而設置綁定。如果GetAll()方法返回期望的數據,一切沒有問題。如果拋出異常,這事異常從窗口的構造函數中的InitializeComponent();向上傳輸,顯示此窗口的程式碼就要捕獲這個異常,即使在構造函數中捕獲這個異常,其餘程式碼也無法正常展示。

  1. 非同步支援

只要將IsAsynchronous=”True”就可以在後台進程中執行工作。

非同步數據綁定

WPF還通過綁定每個對象的IsAsyn屬性來提供非同步支援。WPF非同步地從數據對象檢索綁定屬性,數據對象自身仍然是同步創建的。一旦創建集合,綁定就會非同步地從Phone對象中查詢相關屬性,這個過程沒有什麼意義。利用這一屬性的唯一方法是構建在屬性獲取過程中添加耗時邏輯的特殊類,例如,考慮一個綁定到數據模型的分析應用程式。數據對象可能包含一部分資訊,使用耗時計算對其進行計算。可使用非同步綁定這種屬性,並使用同步綁定綁定其他屬性,應用程式中的一些資訊會立即顯示,其他資訊會在準備後顯示。WPF還提供了綁定優先順序,基於非同步綁定可以優先綁定一些屬性。

XmlDataProvider

XmlDataProvider數據提供者被設計成只讀的,不具有提交數據修改的能力,而且不能處理來自其他源的XML數據(資料庫記錄、Web服務消息等)。如果預見到需要修改XML或者需要將XML數據轉換成程式碼中能夠使用的對象形式,最好使用XML擴展支援。如果數據是以XML形式存儲,然後由頁面支援展示,那麼XmlDataProvider是最合理的選擇。

數據轉換

WPF提供了2種工具,可以進行數據轉換:

  • 字元串格式化:可以設置Binding的StringFormat屬性對文本形式的數據進行轉換(例如包含日期和數字的字元串)
  • 值轉換器:功能強大,使用該功能可以將任意類型的數據源轉換為任意類型的對象表示,然後傳遞到關聯的控制項

字元串轉換器

具體形式為{0:C}。其中0代表第一個數值,C表示希望的數據格式。完整的格式是 {}{0:C},程式碼如下:

<StackPanel Orientation="Horizontal">
    <TextBlock Text="{Binding Date, StringFormat={}{0:F}}"/>
    <TextBlock Text="{Binding Date, StringFormat=當前日期:{0:F}}"/>
</StackPanel>

值轉換器簡介

值轉換器負責在目標中顯示數據之前轉換源數據,並且對於雙向綁定在將數據應用回源之前轉換新的目標值。使用方式如下:

  • 將數據格式化為字元串表示形式。例如將數字轉換成貨幣字元串
  • 創建特定類型的WPF對象。讀取二進位數據並將其轉換成BitmapImage對象,綁定到Image控制項
  • 根據綁定數據有條件地改變元素中的屬性。
  1. 使用值轉換器設置字元串的格式

創建值轉換器,主要有4個步驟:

  • 創建一個實現了IValueConverter介面的類
  • 為該類聲明添加ValueConversion特性,並制定目標數據類型(可選)
  • 實現Convert方法,將原來的格式轉換為顯示的格式
  • 實現ConvertBack方法,該方法實現反向變換,將值從顯示格式轉換為原格式
public class EyeTypeForegroundConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        Brush output = ResourceHelper.GetResource<Brush>("PrimaryReverseTextBrush");
        //Brush output = (Brush)Application.Current.Resources["PrimaryReverseTextBrush"];

        if (value != null && value != DependencyProperty.UnsetValue && parameter != null && parameter != DependencyProperty.UnsetValue)
        {
            int param = System.Convert.ToInt32(parameter);
            int eyeType = System.Convert.ToInt32(value);

            if (param == eyeType) output = ResourceHelper.GetResource<Brush>("InfoBrush");
        }

        return output;
    }

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

  1. 使用值轉換器創建對象

最典型的應用就是從二進位數據轉換BitmapImage對象,用於Image控制項展示影像

public class Path2BitmapImageConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value == null) return null;

        if (value is string imagePath)
        {
            //return imagePath;
            return ImageShareHelper.File2BitmapImage(imagePath);
        }
        return null;
    }

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

public static BitmapImage File2BitmapImage(string path)
{
    if (!string.IsNullOrEmpty(path) && File.Exists(path) && (Path.GetExtension(path).Equals(".bmp") || Path.GetExtension(path).Equals(".jpeg") || Path.GetExtension(path).Equals(".jpg") || Path.GetExtension(path).Equals(".png")))
    {
        BitmapImage bitmap = new BitmapImage();
        bitmap.BeginInit();
        bitmap.CacheOption = BitmapCacheOption.OnLoad;
        bitmap.StreamSource = new MemoryStream(File.ReadAllBytes(path));
        bitmap.EndInit();
        bitmap.Freeze();
        return bitmap;
    }
    else
    {
        return null;
    }
}

  1. 應用條件格式化

有些轉換器不是為了顯示格式化的數據,而是根據不同條件展示不同數據

public class DtoShowStringConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null) return null;

            string output = null;

            {
                if (value is DateTime input)
                {
                    if (parameter == null)
                    {
                        output = input.ToString();
                    }
                    else//, ConverterParameter=date, 此處獲得是字元串類型
                    {
                        var param = parameter.ToString();
                        output = input.ToShortDateString();
                    }
                }
            }
            {
                if (value is SexType input)
                {
                    switch (input)
                    {
                        case SexType.Female:
                            output = Properties.Langs.Lang.SexFemale;
                            break;
                        case SexType.Male:
                            output = Properties.Langs.Lang.SexMale;
                            break;
                        case SexType.Null:
                            output = Properties.Langs.Lang.SexNull;
                            break;
                        default:
                            break;
                    }
                }
            }
            {
                if (value is ExamFileType input)
                {
                    switch (input)
                    {
                        case ExamFileType.Jpg:
                            output = Properties.Langs.Lang.ExamFileTypeJpg;
                            break;
                        case ExamFileType.Avi:
                            output = Properties.Langs.Lang.ExamFileTypeAvi;
                            break;
                        case ExamFileType.Data:
                            output = Properties.Langs.Lang.ExamFileTypeData;
                            break;
                        default:
                            break;
                    }
                }
            }
            {
                if (value is EyeType input)
                {
                    switch (input)
                    {
                        case EyeType.Right:
                            output = Properties.Langs.Lang.EyeTypeRight;
                            break;
                        case EyeType.Left:
                            output = Properties.Langs.Lang.EyeTypeLeft;
                            break;
                        case EyeType.Both:
                            output = Properties.Langs.Lang.EyeTypeBoth;
                            break;
                        case EyeType.Unknown:
                            output = Properties.Langs.Lang.EyeTypeUnknown;
                            break;
                        default:
                            break;
                    }
                }
            }
            {
                if (value is ExamType input)
                {
                    switch (input)
                    {
                        case ExamType.SW8800:
                            output = Properties.Langs.Lang.ExamTypeSW8800;
                            break;
                        case ExamType.SW8000:
                            output = Properties.Langs.Lang.ExamTypeSW8000;
                            break;
                        case ExamType.SW6000D:
                            output = Properties.Langs.Lang.ExamTypeSW6000D;
                            break;
                        case ExamType.SW6000:
                            output = Properties.Langs.Lang.ExamTypeSW6000;
                            break;
                        case ExamType.SW9000:
                            output = Properties.Langs.Lang.ExamTypeSW9000;
                            break;
                        case ExamType.SW7000:
                            output = Properties.Langs.Lang.ExamTypeSW7000;
                            break;
                        case ExamType.SW6000A:
                            output = Properties.Langs.Lang.ExamTypeSW6000A;
                            break;
                        case ExamType.SW9800:
                            output = Properties.Langs.Lang.ExamTypeSW9800;
                            break;
                        case ExamType.SW4000:
                            output = Properties.Langs.Lang.ExamTypeSW4000;
                            break;
                        case ExamType.SW4000T:
                            output = Properties.Langs.Lang.ExamTypeSW4000A;
                            break;
                        case ExamType.SW900:
                            output = Properties.Langs.Lang.ExamTypeSW900;
                            break;
                        case ExamType.SW8800T:
                            output = Properties.Langs.Lang.ExamTypeSW8800T;
                            break;
                        case ExamType.SW9600:
                            output = Properties.Langs.Lang.ExamTypeSW9600;
                            break;
                        default:
                            break;
                    }
                }
            }
            {
                if (value is RoleType input)
                {
                    switch (input)
                    {
                        case RoleType.Admin:
                            output = Properties.Langs.Lang.UserRoleTypeAdmin;
                            break;
                        case RoleType.Company:
                            output = Properties.Langs.Lang.UserRoleTypeCompany;
                            break;
                        case RoleType.Doctor:
                            output = Properties.Langs.Lang.UserRoleTypeDoctor;
                            break;
                        default:
                            break;
                    }
                }
            }
            {
                if (value is LangType input)
                {
                    switch (input)
                    {
                        case LangType.zh:
                            output = Properties.Langs.Lang.LanguageZh;
                            break;
                        case LangType.en:
                            output = Properties.Langs.Lang.LanguageEn;
                            break;
                        case LangType.fa:
                            output = Properties.Langs.Lang.LanguageFa;
                            break;
                        case LangType.fr:
                            output = Properties.Langs.Lang.LanguageFr;
                            break;
                        default:
                            break;
                    }
                }
            }
            {
                if (value is IOLFormulaType input)
                {
                    switch (input)
                    {
                        case IOLFormulaType.SRKII:
                            output = Properties.Langs.Lang.DeviceIolMasterIOLFormulaSRKII;
                            break;
                        case IOLFormulaType.SRKT:
                            output = Properties.Langs.Lang.DeviceIolMasterIOLFormulaSRKT;
                            break;
                        case IOLFormulaType.Holladay:
                            output = Properties.Langs.Lang.DeviceIolMasterIOLFormulaHolladay;
                            break;
                        case IOLFormulaType.BinkhorstII:
                            output = Properties.Langs.Lang.DeviceIolMasterIOLFormulaBinkhorstII;
                            break;
                        case IOLFormulaType.HofferQ:
                            output = Properties.Langs.Lang.DeviceIolMasterIOLFormulaHofferQ;
                            break;
                        case IOLFormulaType.Haigis:
                            output = Properties.Langs.Lang.DeviceIolMasterIOLFormulaHaigis;
                            break;
                        case IOLFormulaType.HaigisL:
                            output = Properties.Langs.Lang.DeviceIolMasterIOLFormulaHaigisL;
                            break;
                        case IOLFormulaType.ShammasPL:
                            output = Properties.Langs.Lang.DeviceIolMasterIOLFormulaShammas;
                            break;
                        case IOLFormulaType.Masket:
                            output = Properties.Langs.Lang.DeviceIolMasterIOLFormulaMasket;
                            break;
                        case IOLFormulaType.ModifiedMasket:
                            output = Properties.Langs.Lang.DeviceIolMasterIOLFormulaModifiedMasket;
                            break;
                        default:
                            break;
                    }
                }
            }
            {
                if (value is double input)
                {
                    if (parameter != null && parameter is string digits)
                    {
                        double roundDouble = (double)Math.Round((decimal)input, System.Convert.ToInt32(digits), MidpointRounding.AwayFromZero);
                        output = roundDouble.ToString(string.Format("f{0}", System.Convert.ToInt32(digits)));
                    }
                    else
                    {
                        output = input.ToString();
                    }
                }
            }
            return output;
        }

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

  1. 評估多個屬性

到目前為止,已經使用綁定表達式將一部分源數據轉換成單個格式化的結果。也可以創建能夠評估或者結合多個源屬性資訊的綁定

/// <summary>
/// 多綁定 有一個為true 則顯示,否則隱藏,參數為0時,邏輯翻轉
/// </summary>
public class MultiOrEqualVisibleConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        Visibility output = Visibility.Collapsed;

        if (values != null && values.Count() > 0)
        {
            {
                bool param = true;
                if (parameter != null)
                {
                    param = System.Convert.ToBoolean(parameter);
                }
                var count = values.Where(p => p != DependencyProperty.UnsetValue && p != null && System.Convert.ToBoolean(p)).Count();
                if (count == 0)
                {
                    output = param ? Visibility.Collapsed : Visibility.Visible;
                }
                else
                {
                    output = param ? Visibility.Visible : Visibility.Collapsed;
                }
            }
        }
        return output;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}