zoukankan      html  css  js  c++  java
  • Windows 10 开发日记(五)-- 当Binding遇到异步 -- 解决方案


    前文再续,上一章提出了问题,本章提出了三种解决方案:

    解决方案一:手动进行异步转换,核心思想:将binding做的事情放入CodeBehind

    FilterItemControl.XAML:

        <Grid>
            <Image x:Name="FilterImage" Stretch="UniformToFill"/>
            <Grid VerticalAlignment="Bottom" Height="20">
                <TextBlock x:Name="FilterName" TextWrapping="Wrap" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White"/>
            </Grid>
            <Border x:Name="border" BorderBrush="White" BorderThickness="1" d:LayoutOverrides="LeftPosition, RightPosition, TopPosition, BottomPosition" Margin="1" Visibility="Collapsed"/>
        </Grid>
    

      

    FilterItemControl.cs

     /// <summary>
            /// 设置数据源
            /// </summary>
            /// <param name="filter"></param>
            public async void SetSource(Filter filter)
            {
                if (filter != null)
                {
                    _filter = filter;
    
                    // 使用WriteableBitmap有一个不好的点:必须要知道图片的大小
                    WriteableBitmap result = new WriteableBitmap(768, 1280);
                    var wbData = await MyFilterSDK.ProcessFilterAsync(filter);
                    if(wbData != null)
                    {
                        using (var bmpStream = result.PixelBuffer.AsStream())
                        {
                            bmpStream.Seek(0, SeekOrigin.Begin);
                            bmpStream.Write(wbData, 0, (int)bmpStream.Length);
                        }
                        FilterImage.Source = result;
                    }
                    FilterName.Text = filter.FilterName;
                }
            }
    

    为其设置数据源, FilterItemsControl.cs

    /// <summary>
            /// 数据源发生变化
            /// </summary>
            /// <param name="filters">滤镜列表</param>
            private void OnItemsSourceChanged(List<Filter> filters)
            {
                if(filters != null)
                {
                    Container.Children.Clear();
    
                    foreach(var filter in filters)
                    {
                        FilterItemControl itemcontrol = new FilterItemControl();
                        itemcontrol.Width = ITEMWIDTH;
                        itemcontrol.Height = ITEMHEIGHT;
                        // 将binding中做的事情放到代码中!
                        itemcontrol.SetSource(filter);
    itemcontrol.ItemSelected += Itemcontrol_ItemSelected; itemcontrol.ItemDoubleClicked += Itemcontrol_ItemDoubleClicked; Container.Children.Add(itemcontrol); } } }

      优点:方便简单

          缺点:XAML必须写死,没有扩展性,如果同样的数据变换一种显示方式,需要重写一个控件。

    解决方案二:使用异步属性,核心思想,使用Binding和异步加载

    FilterItemControl.xaml

        <Grid>
            <Image x:Name="FilterImage" Stretch="UniformToFill" Source="{Binding WBAsyncProperty.AsyncValue, Converter={StaticResource imagConverter}}"/>
            <Grid VerticalAlignment="Bottom" Height="20">
                <TextBlock x:Name="FilterName" TextWrapping="Wrap" Text="{Binding FilterName}" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White"/>
            </Grid>
            <Border x:Name="border" BorderBrush="White" BorderThickness="1" d:LayoutOverrides="LeftPosition, RightPosition, TopPosition, BottomPosition" Margin="1" Visibility="Collapsed"/>
        </Grid>
    

    FilterItemControl.cs不需要额外的东西,但是Model层的数据源需要增加一个异步属性用于被View层绑定

    Filter.cs

            private AsyncProperty<byte[]> _wbAsyncProperty; // 异步属性
            public AsyncProperty<byte[]> WBAsyncProperty
            {
                get
                {
                    return _wbAsyncProperty;
                }
                set
                {
                    SetProperty(ref _wbAsyncProperty, value);
                }
            } 
    
         // 初始化 public Filter() { WBAsyncProperty = new AsyncProperty<byte[]>(async () => { var result = await MyFilterSDK.ProcessFilterAsync(this); return result; }); }

      

    由于返回值是byte[]类型,所以我们在binding时,必须要进行一次转换,将其转换为WriteableBitmap:BytesToImageConverter.cs

    public class BytesToImageConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, string language)
            {
                // 使用WriteableBitmap有一个不好的点:必须要知道图片的大小
                WriteableBitmap result = new WriteableBitmap(768, 1280);
                var filterData = value as byte[];
    
                if (filterData != null)
                {
                    #region  WriteableBitmap方案
                    using (var bmpStream = result.PixelBuffer.AsStream())
                    {
                        bmpStream.Seek(0, SeekOrigin.Begin);
                        bmpStream.Write(filterData, 0, (int)bmpStream.Length);
                        return result;
                    }
                    #endregion
                }
                else
                    return null;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, string language)
            {
                throw new NotImplementedException();
            }
        }
    

      关于如何实现AsyncProperty和其工作原理在这里不做深究,在这里总结一下这个方案的优缺点:

    优点:使用Binding,UI上不会卡顿,图片获取完之后会显示在UI上

    缺点: 1. 控件重用性不高

            2. SDK必须与UI无关,这也是为什么返回byte[],而不是直接返回WrieableBitmap的原因,与AsyncProperty的实现技术有关

            3. 因为原因2,必须实现转换器

    解决方案三:使用DataTemplate,核心思想:将DataTemplate转换放到CodeBehind

     FilterItemControl.XAML需要改变,这里只需要一个ContentPresenter接收内容

       <Grid>  
    <ContentPresenter x:Name="Presenter"/>
            <Border x:Name="border" BorderBrush="White" BorderThickness="1" d:LayoutOverrides="LeftPosition, RightPosition, TopPosition, BottomPosition" Margin="1" Visibility="Collapsed"/>
        </Grid>
    

    FilterItemControl.cs需要增加一个ContentTemplate,用于获取应用在这个控件上的模板,并且根据模板把UI显示出来:

       public DataTemplate ContentDataTemplate
            {
                get { return (DataTemplate)GetValue(ContentDataTemplateProperty); }
                set { SetValue(ContentDataTemplateProperty, value); }
            }
    
            public static readonly DependencyProperty ContentDataTemplateProperty =
                DependencyProperty.Register("ContentDataTemplate", typeof(DataTemplate), typeof(FilterItemControl3), new PropertyMetadata(null,OnContentDataTemplateChanged));
    
    
            private static void OnContentDataTemplateChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
            {
                FilterItemControl3 owner = sender as FilterItemControl3;
                owner.OnContentDataTemplateChanged(args.NewValue as DataTemplate);
            }
    
            private async void OnContentDataTemplateChanged(DataTemplate newDataTemplate)
            {
                UIElement rootElement = newDataTemplate.LoadContent() as UIElement;
                if(rootElement != null)
                {
                    Image img = VisualTreeExtensions.FindFirstElementInVisualTree<Image>(rootElement);
    
                    if (img != null)
                    {
                        #region 使用SDK 处理
                        WriteableBitmap result = new WriteableBitmap(768, 1280);
                        var wbData = await MyFilterSDK.ProcessFilterAsync(this.DataContext as Filter);
                        if (wbData != null)
                        {
                            using (var bmpStream = result.PixelBuffer.AsStream())
                            {
                                bmpStream.Seek(0, SeekOrigin.Begin);
                                bmpStream.Write(wbData, 0, (int)bmpStream.Length);
                            }
                            img.Source = result;
                        }
                        #endregion
                        // 改变了图片之后,需要将其加入到可视化中以显示,如果不加这一步你可以想象会出现什么情况
                        Presenter.Content = rootElement;
                    }
                }
            }
    

    同样的,需要修改FilterItemsControl.cs,增加一个ItemDataTemplate传递给FilterItemControl:

            /// <summary>
            /// 子项的模板
            /// </summary>
            public DataTemplate ItemDataTemplate
            {
                get { return (DataTemplate)GetValue(ItemDataTemplateProperty); }
                set { SetValue(ItemDataTemplateProperty, value); }
            }
    
            public static readonly DependencyProperty ItemDataTemplateProperty =
                DependencyProperty.Register("ItemDataTemplate", typeof(DataTemplate), typeof(FilterItemsControl3), new PropertyMetadata(0));
    
    /// <summary>
            /// 数据源发生变化
            /// </summary>
            /// <param name="filters">滤镜列表</param>
            private void OnItemsSourceChanged(List<Filter> filters)
            {
                if (filters != null)
                {
                    Container.Children.Clear();
    
                    foreach (var filter in filters)
                    {
                        FilterItemControl3 itemcontrol = new FilterItemControl3();
                        //itemcontrol.Width = ITEMWIDTH; // 不要了,在DataTemplate中指定
                        //itemcontrol.Height = ITEMHEIGHT;
    
                        //1. 设置DataContext
                        itemcontrol.DataContext = filter;
                        //2. 设置模板
                        itemcontrol.ContentDataTemplate = ItemDataTemplate;
    
                        itemcontrol.ItemSelected += Itemcontrol_ItemSelected;
                        itemcontrol.ItemDoubleClicked += Itemcontrol_ItemDoubleClicked;
                        Container.Children.Add(itemcontrol);
                    }
                }
            }
    

    那么我们只需要在使用这个控件的地方编写一个ItemDataTemplate就可以了:

    <local:FilterItemsControl3 x:Name="FilterItemsUserControl" Opacity="0" RenderTransformOrigin="0.5,0.5" Margin="0">
                    <local:FilterItemsControl3.RenderTransform>
                        <CompositeTransform TranslateY="100"/>
                    </local:FilterItemsControl3.RenderTransform>
                    <local:FilterItemsControl3.ItemDataTemplate>
                        <DataTemplate>
                            <Grid Width="80" Height="80">
                                <Image x:Name="SourceImage"/>
                                <Grid Height="20" VerticalAlignment="Top" Background="#7F000000">
                                    <TextBlock x:Name="textBlock" TextWrapping="Wrap" Text="{Binding FilterName}" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White"/>
                                </Grid>
                            </Grid>
                        </DataTemplate>
                    </local:FilterItemsControl3.ItemDataTemplate>
                </local:FilterItemsControl3>
    

     第三种方案是我想表达的,但是我们看出来,它也并不是最优的,需要在代码中取出DataTemplate中的可视元素,然后将SDK处理过的图片放到目标Image控件的Source中去,但是他的优点也是有的: 1. UI的可扩展性

                                 2. 与异步无关的属性可以通过Binding展示

    可以说,方案三模拟了DataTemplate如何应用在一个控件上的,这也是我想从这个例子中总结的东西:

    1. DataTemplate的作用

    2. 控件在应用了DataTemplate之后发生了什么?

    3. 通过DataTemplate.LoadContent(), 获取控件,并且修改控件,如果不使用Presenter.Content = rootElement, 为什么没有反应?

    总结:

    1. 首先DataTemplate的MSDN的解释非常清楚,就是将“数据"转换为可见的元素,这也是为什么我们选择DataTemplate来展示Filter的原因。

    2. 控件在应用了DataTemplate之后会发生什么?因为微软的封闭,我们看不到,但是可以猜到,它的实现类似于我们方案三的实现:取得DataTemplate中的元素,并且将其加载到可视化树中显示。我们在XAML中写的DataTemplate类似于一个类的声明,当某个控件需要这个DataTemplate时,会new 一个实例,然后目标控件,并且替换它之前的可视化树。

    3. 第三个问题的答案基于第二个问题:通过DataTemplate.LoadContent()获得的UIElement每次都是不一样的,就是说调用该方法就类似与调用 new DataTemplate(),一样,只是一次实例化,此时的元素并没有加载到可视化树中(可以通过GetHashCode()对比),所以,无论做什么修改,你都看不出结果。所以必须要有Presenter.Content = rootElement这关键的一步。

    Demo已经写好,VS2015工程,WU框架,PC运行。

    MyFilterDemo.rar

  • 相关阅读:
    app专项测试之稳定性测试-monkey测试
    Mac下Fiddler的安装启动。
    测试环境搭建和部署(在Linux环境下搭建jdk+Tomcat+mysql环境和项目包的部署)
    mysql如何用sql语句修改表字段?
    VMware虚拟机出现“正在使用中”如何解决?
    Opencv2.1+vs2008生成不依赖编译环境的exe文件
    VS2008在win7下不时出现Microsoft Incremental Linker已停止工作
    没有找到opencv_core231d.dll运行结果不能显示
    Mat类型与IplImage类型之间相互转换
    获取当前可执行文件的路径(绝对路径)
  • 原文地址:https://www.cnblogs.com/DinoWu/p/4549770.html
Copyright © 2011-2022 走看看