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();
    }
}

原文地址:https://www.cnblogs.com/vigorous/p/15493534.html