Windows 10 开发日记(五)-- 当Binding遇到异步 -- 解决方案
前文再續(xù),上一章提出了問題,本章提出了三種解決方案:
?
解決方案一:手動進(jìn)行異步轉(zhuǎn)換,核心思想:將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>/// 設(shè)置數(shù)據(jù)源/// </summary>/// <param name="filter"></param>public async void SetSource(Filter filter){if (filter != null){_filter = filter;// 使用WriteableBitmap有一個不好的點(diǎn):必須要知道圖片的大小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;}}
為其設(shè)置數(shù)據(jù)源, FilterItemsControl.cs
/// <summary>/// 數(shù)據(jù)源發(fā)生變化/// </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);}}}
優(yōu)點(diǎn):方便簡單
? ? ? 缺點(diǎn):XAML必須寫死,沒有擴(kuò)展性,如果同樣的數(shù)據(jù)變換一種顯示方式,需要重寫一個控件。
?
解決方案二:使用異步屬性,核心思想,使用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層的數(shù)據(jù)源需要增加一個異步屬性用于被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時,必須要進(jìn)行一次轉(zhuǎn)換,將其轉(zhuǎn)換為WriteableBitmap:BytesToImageConverter.cs
public class BytesToImageConverter : IValueConverter{public object Convert(object value, Type targetType, object parameter, string language){// 使用WriteableBitmap有一個不好的點(diǎn):必須要知道圖片的大小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}elsereturn null;}public object ConvertBack(object value, Type targetType, object parameter, string language){throw new NotImplementedException();}}
關(guān)于如何實(shí)現(xiàn)AsyncProperty和其工作原理在這里不做深究,在這里總結(jié)一下這個方案的優(yōu)缺點(diǎn):
優(yōu)點(diǎn):使用Binding,UI上不會卡頓,圖片獲取完之后會顯示在UI上
缺點(diǎn): 1. 控件重用性不高
? ? ? ? 2. SDK必須與UI無關(guān),這也是為什么返回byte[],而不是直接返回WrieableBitmap的原因,與AsyncProperty的實(shí)現(xiàn)技術(shù)有關(guān)
? ? ? ? 3. 因?yàn)樵?,必須實(shí)現(xiàn)轉(zhuǎn)換器
?
解決方案三:使用DataTemplate,核心思想:將DataTemplate轉(zhuǎn)換放到CodeBehind
?FilterItemControl.XAML需要改變,這里只需要一個ContentPresenter接收內(nèi)容
<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,用于獲取應(yīng)用在這個控件上的模板,并且根據(jù)模板把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// 改變了圖片之后,需要將其加入到可視化中以顯示,如果不加這一步你可以想象會出現(xiàn)什么情況Presenter.Content = rootElement;}}}
同樣的,需要修改FilterItemsControl.cs,增加一個ItemDataTemplate傳遞給FilterItemControl:
/// <summary>/// 子項(xiàng)的模板/// </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>/// 數(shù)據(jù)源發(fā)生變化/// </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. 設(shè)置DataContextitemcontrol.DataContext = filter;//2. 設(shè)置模板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>
第三種方案是我想表達(dá)的,但是我們看出來,它也并不是最優(yōu)的,需要在代碼中取出DataTemplate中的可視元素,然后將SDK處理過的圖片放到目標(biāo)Image控件的Source中去,但是他的優(yōu)點(diǎn)也是有的: 1. UI的可擴(kuò)展性
? ? ? ? ? ? ? ? ? ? ? ? ? ? ?2. 與異步無關(guān)的屬性可以通過Binding展示
可以說,方案三模擬了DataTemplate如何應(yīng)用在一個控件上的,這也是我想從這個例子中總結(jié)的東西:
1. DataTemplate的作用
2. 控件在應(yīng)用了DataTemplate之后發(fā)生了什么?
3. 通過DataTemplate.LoadContent(), 獲取控件,并且修改控件,如果不使用Presenter.Content = rootElement, 為什么沒有反應(yīng)?
?
總結(jié):
1. 首先DataTemplate的MSDN的解釋非常清楚,就是將“數(shù)據(jù)"轉(zhuǎn)換為可見的元素,這也是為什么我們選擇DataTemplate來展示Filter的原因。
2. 控件在應(yīng)用了DataTemplate之后會發(fā)生什么?因?yàn)槲④浀姆忾],我們看不到,但是可以猜到,它的實(shí)現(xiàn)類似于我們方案三的實(shí)現(xiàn):取得DataTemplate中的元素,并且將其加載到可視化樹中顯示。我們在XAML中寫的DataTemplate類似于一個類的聲明,當(dāng)某個控件需要這個DataTemplate時,會new 一個實(shí)例,然后目標(biāo)控件,并且替換它之前的可視化樹。
3. 第三個問題的答案基于第二個問題:通過DataTemplate.LoadContent()獲得的UIElement每次都是不一樣的,就是說調(diào)用該方法就類似與調(diào)用 new DataTemplate(),一樣,只是一次實(shí)例化,此時的元素并沒有加載到可視化樹中(可以通過GetHashCode()對比),所以,無論做什么修改,你都看不出結(jié)果。所以必須要有Presenter.Content = rootElement這關(guān)鍵的一步。
?
Demo已經(jīng)寫好,VS2015工程,WU框架,PC運(yùn)行。
MyFilterDemo.rar
轉(zhuǎn)載于:https://www.cnblogs.com/DinoWu/p/4549770.html
總結(jié)
以上是生活随笔為你收集整理的Windows 10 开发日记(五)-- 当Binding遇到异步 -- 解决方案的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 苹果手机换卡槽多少钱
- 下一篇: redmine忘记username和pa