Windows 10 開發日記(五)-- 當Binding遇到非同步 -- 解決方案

來源:互聯網
上載者:User

標籤:


前文再續,上一章提出了問題,本章提出了三種解決方案:

 

解決方案一:手動進行非同步轉換,核心思想:將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

Windows 10 開發日記(五)-- 當Binding遇到非同步 -- 解決方案

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.