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

时间: 2024-10-17 00:12:57

Windows 10 开发日记(五)-- 当Binding遇到异步 -- 解决方案的相关文章

Windows 10 开发日记(四)-- 当Binding遇到异步 -- 问题的引出

Binding之于MVVM来说的重要性无需多说,Binding之于DataTemplate来说的重要性也无需多说,Binding的重要性也无需多说,异步也不用多说了,今天就到此为止吧... -------------------------------------------------冷冷的分割线------------------------------------------------------ 但是,当你要binding的数据是一个需要异步操作的结果的时候呢? 这是我们在项目中遇到的

Windows 10 开发日记(三)-- 如何在手势开始之前判断手指数量

这是我们在实际项目中经常要面临的问题,因为你很可能会出现这种需求:单手操作与双手操作的行为不同,在手势开始之时就需要知道到底是单手还是双手. 方案一:   了解了这个需求之后,我迅速凭借经验,感觉在ManipulationStarting或者ManipulationStarted事件传入的参数中应该类似于e.GetPointers()类似的方法,用于获得当前有多少个手指在屏幕上.感觉三年的Windows 开发经验终于有了点小用,节省了我好多效率不免心生有预感...但是当寻找了半天之后,胸中顿时有

Windows 10 开发日记(二)-- 手势顺序调研

前文再续,书接上一回. 上回说到Windows10手势分为高中低三个档次,于是心血来潮想要研究一下这三个等级的操作顺序是如何的?Manipulation手势的几个事件的触发顺序又是怎么样的,什么时候触发Starting,什么时候触发Started?UserControl自身有一个虚方法:OnManipulationStarting,OnManipulationStarted,它同时又有ManipulationStarted,ManipulationStarted事件,为什么功能类似的东西要有不同

Windows 10开发基础——XML和JSON (二)

主要内容: Linq to XML Newtonsoft.Json.Linq来解析JSON 博客园RSS(http://www.cnblogs.com/rss)的解析 UWP调用自己实现的Web API 1.Linq to XML     Linq to XML不是一个新鲜的话题了,网上以及各种资料对这个介绍都比较多.今天就简单了解下,不做深入的研究...在代码中生成XML文档,使用Linq to XML会比Windows.Data.Xml.Dom命名空间下的类简单,使用起来也更加灵活.Linq

Windows移动开发(五)——初始XAML

关于具体的基本功就先说这么多,后面遇到再补充说明,前面说的都是一些代码和原理方面的东西,接下来说的会有界面和代码结合,会有成就感,因为能真正的做出东西来了. Windows移动开发包括Windows Store和Windows Phone,Windows Store程序主要针对的是Surface,Windows Phone主要是Win8 Metro手机应用,这两个产品的开发模式基本类似,和Silverlight很像,只是Silverlight是运行在浏览器中,如果有Silverlight基础的A

Windows 10开发基础——文件、文件夹和库(一)

主要内容: 1.枚举查询文件和文件夹 2.文本文件读写的三种方法——创建写入和读取文件 3.获得文件的属性 枚举查询文件和文件夹 先了解一下文件查询的几个方法: StorageFolder.GetFilesAsync: 获取当前文件夹中的所有文件,返回一个 IReadOnlyList<StorageFile>集合 IReadOnlyList<StorageFile> fileList =await picturesFolder.GetFilesAsync(); StorageFol

(OpenCV) VS2013 + opencv-2.4.10.exe + Windows 10 开发环境配置

主要配置2点: - Windows 环境变量. - VC++ 配置. STEP BY STEP: 1. 双击 ”opencv-2.4.10.exe“,解压到本地文件夹 “C:\ ". 2. 设置环境变量: 2.1) 新建User variables: Variable name: OPENCV, Variable value:  C:\opencv\build 2.2) 编辑 System variables 中的 Path 变量, 添加: %OPENCV%\x86\vc12\bin 3. 设置

Windows 10开发基础——指针事件和操作事件(一)

主要内容: 1.指针事件 2.操作事件 1.指针事件 指针事件由各种活动输入源引发,包括触摸.触摸板.笔和鼠标(它们替代传统的鼠标事件).指针事件基于单一输入点(手指.笔尖.鼠标光标),但不支持基于速度的交互.下面是指针事件列表及其相关的事件参数列表: 事件或类 描述 PointerPressed 单根手指触摸屏幕时发生. PointerReleased 该同一触摸接触抬起时发生. PointerMoved 在屏幕上拖动指针时发生. PointerEntered 在指针进入元素的点击测试区时发生

Windows 10开发基础——启动默认应用的URI

主要内容:通过指定的URI来启动默认的应用(设置,应用商店,地图,人脉) 方法一:直接在XAML中添加如下代码 <TextBlock x:Name="LocationDisabledMessage" FontStyle="Italic" Visibility="Visible" Margin="0,150,0,0" TextWrapping="Wrap" > <Run Text="