最近在做今日头条WP的过程中,遇到需要动态生成Pivot项的问题。第一个版本是把几个频道写死在xaml里了,事件绑定也写在xaml里,每个频道绑定一个ObservableCollection<ArticleItem>。xaml中一个Pivot项的代码大体如下:
<phone:PivotItem Header="热点"> <Grid Margin="12,0,0,0" > <Grid.RowDefinitions> <RowDefinition Height="*" /> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <telerikPrimitives:RadDataBoundListBox UseOptimizedManipulationRouting="False" DataVirtualizationMode="OnDemandAutomatic" IsPullToRefreshEnabled="True" EmptyContent="" IsAsyncBalanceEnabled="True" x:Name="radListBoxHot" Margin="0" CacheMode="BitmapCache" ItemsSource="{Binding ArticleItemListHot}" telerikCore:InteractionEffectManager.IsInteractionEnabled="True" ItemTemplate="{StaticResource ArticleItemDataTemplate}" > <i:Interaction.Triggers> <i:EventTrigger EventName="ItemTap"> <i:InvokeCommandAction Command="{Binding CommandNavToArticleDetail}" CommandParameter="{Binding SelectedItem, ElementName=radListBoxHot}" /> </i:EventTrigger> <i:EventTrigger EventName="DataRequested"> <i:InvokeCommandAction Command="{Binding CommandDataRequestedArticle}" CommandParameter="Hot"/> </i:EventTrigger> <i:EventTrigger EventName="RefreshRequested" > <i:InvokeCommandAction Command="{Binding CommandRefreshRequestedArticle}" CommandParameter="Hot"/> </i:EventTrigger> <ec:PropertyChangedTrigger Binding="{Binding IsUIBusy}"> <i:Interaction.Behaviors> <ec:ConditionBehavior> <ec:ConditionalExpression> <ec:ComparisonCondition LeftOperand="{Binding IsUIBusy}" RightOperand="False"/> </ec:ConditionalExpression> </ec:ConditionBehavior> </i:Interaction.Behaviors> <action:StopPullToRefreshLoadingAction TargetObject="{Binding ElementName=radListBoxHot}"/> </ec:PropertyChangedTrigger> </i:Interaction.Triggers> <telerikPrimitives:RadDataBoundListBox.ItemAddedAnimation> <telerikCore:RadFadeAnimation StartOpacity="0" InitialDelay="0:0:0.3" EndOpacity="1" Duration="0:0:0.9"> <telerikCore:RadFadeAnimation.Easing> <CubicEase EasingMode="EaseOut" /> </telerikCore:RadFadeAnimation.Easing> </telerikCore:RadFadeAnimation> </telerikPrimitives:RadDataBoundListBox.ItemAddedAnimation> <telerikPrimitives:RadDataBoundListBox.ItemLoadingTemplate> <DataTemplate> <telerikPrimitives:RadBusyIndicator AnimationStyle="AnimationStyle9" IsRunning="{Binding IsUIBusy}" Content="努力加载ing..."/> </DataTemplate> </telerikPrimitives:RadDataBoundListBox.ItemLoadingTemplate> <telerikPrimitives:RadDataBoundListBox.VirtualizationStrategyDefinition> <telerikPrimitives:StackVirtualizationStrategyDefinition Orientation="Vertical" /> </telerikPrimitives:RadDataBoundListBox.VirtualizationStrategyDefinition> </telerikPrimitives:RadDataBoundListBox> </Grid> </phone:PivotItem>
ViewModel中要绑定几个command,实现点击项、下拉刷新、自动加载更多等,具体代码就不贴了,主要是根据CommandParameter来区分是触发哪个列表的事件。
这样实现的话,ViewModel中需要有n个ObservableCollection<ArticleItem>,代码重复的太多。
后来用户要求根据设置自定义首页频道,于是要改成后台代码生成的方式。因为首页枢轴里还有几个Pivot项不是文章列表,所以想在Page_Loaded事件中动态去生成所需的Pivot项,并手动设置绑定。
自定义频道的实体类:
/// <summary> /// 用户固定在首页的自定义频道信息 /// </summary> public class UserCategoryItem : BindableBase<UserCategoryItem> { public UserCategoryItem() { ArticleItemList = new ObservableCollection<ArticleListItem>(); TempArticleItemList = new List<ArticleListItem>(); } /// <summary> /// 频道 /// </summary> public CategoryItem CurrentCategoryItem { get { return _CurrentCategoryItemLocator(this).Value; } set { _CurrentCategoryItemLocator(this).SetValueAndTryNotify(value); } } #region Property CategoryItem CurrentCategoryItem Setup protected Property<CategoryItem> _CurrentCategoryItem = new Property<CategoryItem> { LocatorFunc = _CurrentCategoryItemLocator }; static Func<BindableBase, ValueContainer<CategoryItem>> _CurrentCategoryItemLocator = RegisterContainerLocator<CategoryItem>("CurrentCategoryItem", model => model.Initialize("CurrentCategoryItem", ref model._CurrentCategoryItem, ref _CurrentCategoryItemLocator, _CurrentCategoryItemDefaultValueFactory)); static Func<CategoryItem> _CurrentCategoryItemDefaultValueFactory = () => { return default(CategoryItem); }; #endregion /// <summary> /// 上次获取数据最大时间 /// </summary> public string MaxBehotTime { get { return _MaxBehotTimeLocator(this).Value; } set { _MaxBehotTimeLocator(this).SetValueAndTryNotify(value); } } #region Property string MaxBehotTime Setup protected Property<string> _MaxBehotTime = new Property<string> { LocatorFunc = _MaxBehotTimeLocator }; static Func<BindableBase, ValueContainer<string>> _MaxBehotTimeLocator = RegisterContainerLocator<string>("MaxBehotTime", model => model.Initialize("MaxBehotTime", ref model._MaxBehotTime, ref _MaxBehotTimeLocator, _MaxBehotTimeDefaultValueFactory)); static Func<string> _MaxBehotTimeDefaultValueFactory = () => { return default(string); }; #endregion /// <summary> /// 最小获取时间 /// </summary> public string MinBehotTime { get { return _MinBehotTimeLocator(this).Value; } set { _MinBehotTimeLocator(this).SetValueAndTryNotify(value); } } #region Property string MinBehotTime Setup protected Property<string> _MinBehotTime = new Property<string> { LocatorFunc = _MinBehotTimeLocator }; static Func<BindableBase, ValueContainer<string>> _MinBehotTimeLocator = RegisterContainerLocator<string>("MinBehotTime", model => model.Initialize("MinBehotTime", ref model._MinBehotTime, ref _MinBehotTimeLocator, _MinBehotTimeDefaultValueFactory)); static Func<string> _MinBehotTimeDefaultValueFactory = () => { return default(string); }; #endregion /// <summary> /// 文章内容 /// </summary> public ObservableCollection<ArticleListItem> ArticleItemList { get { return _ArticleItemListLocator(this).Value; } set { _ArticleItemListLocator(this).SetValueAndTryNotify(value); } } #region Property ObservableCollection<ArticleListItem> ArticleItemList Setup protected Property<ObservableCollection<ArticleListItem>> _ArticleItemList = new Property<ObservableCollection<ArticleListItem>> { LocatorFunc = _ArticleItemListLocator }; static Func<BindableBase, ValueContainer<ObservableCollection<ArticleListItem>>> _ArticleItemListLocator = RegisterContainerLocator<ObservableCollection<ArticleListItem>>("ArticleItemList", model => model.Initialize("ArticleItemList", ref model._ArticleItemList, ref _ArticleItemListLocator, _ArticleItemListDefaultValueFactory)); static Func<ObservableCollection<ArticleListItem>> _ArticleItemListDefaultValueFactory = () => { return new ObservableCollection<ArticleListItem>(); }; #endregion /// <summary> /// 排序 /// </summary> public int SortOrder { get { return _SortOrderLocator(this).Value; } set { _SortOrderLocator(this).SetValueAndTryNotify(value); } } #region Property int SortOrder Setup protected Property<int> _SortOrder = new Property<int> { LocatorFunc = _SortOrderLocator }; static Func<BindableBase, ValueContainer<int>> _SortOrderLocator = RegisterContainerLocator<int>("SortOrder", model => model.Initialize("SortOrder", ref model._SortOrder, ref _SortOrderLocator, _SortOrderDefaultValueFactory)); static Func<int> _SortOrderDefaultValueFactory = () => { return default(int); }; #endregion }
在程序首次运行时,如果用户还没有自定义频道,则自动添加默认的,否则从存储中读取,代码写在MainPage.xaml.cs里:
private void MVVMPage_Loaded(object sender, RoutedEventArgs e) { if (pivotMain.Items.Count < 3) { MainPage_Model vm = this.LayoutRoot.DataContext as MainPage_Model; if (vm != null) { vm.IsUIBusy = true; List<UserCategoryItem> listLocal = FileHelper.Open<List<UserCategoryItem>>(Constants.UserCategoryItemListDataFileName); if(listLocal.Any()) { listLocal.ForEach(x => vm.UserCategoryItemList.Add(x)); } if(!vm.UserCategoryItemList.Any()) { UserCategoryItem item1 = new UserCategoryItem { CurrentCategoryItem = new CategoryItem { Name = "推荐", Category = "" }, SortOrder = 0 }; vm.UserCategoryItemList.Add(item1); UserCategoryItem item2 = new UserCategoryItem { CurrentCategoryItem = new CategoryItem { Name = "热点", Category = "news_hot" }, SortOrder = 1 }; vm.UserCategoryItemList.Add(item2); UserCategoryItem item3 = new UserCategoryItem { CurrentCategoryItem = new CategoryItem { Name = "社会", Category = "news_society" }, SortOrder = 2 }; vm.UserCategoryItemList.Add(item3); UserCategoryItem item4 = new UserCategoryItem { CurrentCategoryItem = new CategoryItem { Name = "娱乐", Category = "news_entertainment" }, SortOrder = 3 }; vm.UserCategoryItemList.Add(item4); UserCategoryItem item5 = new UserCategoryItem { CurrentCategoryItem = new CategoryItem { Name = "体育", Category = "news_sports" }, SortOrder = 4 }; vm.UserCategoryItemList.Add(item5); FileHelper.Save<List<UserCategoryItem>>(Constants.UserCategoryItemListDataFileName, vm.UserCategoryItemList.ToList()); } for (int i = 0; i < vm.UserCategoryItemList.Count; i++) { UserCategoryItem item = vm.UserCategoryItemList[i]; //userCategoryItem.CurrentCategoryItem = new CategoryItem { Category = "", Name = "推荐" }; //vm.UserCategoryItemList.Add(userCategoryItem); //手工添加pivot项 PivotItem pivot = new PivotItem(); pivot.Header = item.CurrentCategoryItem.Name; Grid grid = new Grid(); grid.Margin = new Thickness(0, -12, 0, 0); pivot.Content = grid; RadDataBoundListBox listBox = new RadDataBoundListBox(); listBox.Name = string.Format("listBox{0}", i); listBox.UseOptimizedManipulationRouting = false; listBox.DataVirtualizationMode = DataVirtualizationMode.OnDemandAutomatic; listBox.IsPullToRefreshEnabled = true; listBox.IsAsyncBalanceEnabled = true; listBox.ItemsSource = item.ArticleItemList; listBox.ItemTemplate = App.Current.Resources["ArticleItemDataTemplate"] as DataTemplate; listBox.ItemLoadingTemplate = App.Current.Resources["CommonItemLoadingDataTemplate"] as DataTemplate; //listBox.ListHeaderTemplate = App.Current.Resources["CommonListHeaderDataTemplate"] as DataTemplate; listBox.SetValue(InteractionEffectManager.IsInteractionEnabledProperty, true); //listBox.ItemAddedAnimation = App.Current.Resources["CommonItemAddesAnimation"] as RadAnimation; listBox.VirtualizationStrategyDefinition = new StackVirtualizationStrategyDefinition { Orientation = System.Windows.Controls.Orientation.Vertical }; listBox.Margin = new Thickness(12, 0, 0, 0); listBox.EmptyContentTemplate = App.Current.Resources["CommonEmptyDataTemplate"] as DataTemplate; //手动添加EventTrigger //设置目标属性 //BindingOperations.SetBinding(selectedItem, TextBlock.TextProperty, binding); //var invokeCommandAction = new InvokeCommandAction { CommandParameter = "btnAdd" }; var triggers = Interaction.GetTriggers(listBox); //添加ItemTap事件绑定 //#region ItemTap //var itemTapInvokeCommandAction = new InvokeCommandAction(); //// create the command action and bind the command to it //Binding itemTapParameterBinding = new Binding(); ////设置源对象 //itemTapParameterBinding.Source = listBox; ////设置源属性 //itemTapParameterBinding.Path = new PropertyPath("SelectedItem"); ////ElementName与Source只能用一个 ////itemTapParameterBinding.ElementName = listBox.Name; //BindingOperations.SetBinding(itemTapInvokeCommandAction, InvokeCommandAction.CommandParameterProperty, itemTapParameterBinding); //var itemTapEventBinding = new Binding { Path = new PropertyPath("CommandNavToArticleDetail") }; //BindingOperations.SetBinding(itemTapInvokeCommandAction, InvokeCommandAction.CommandProperty, itemTapEventBinding); //// create the event trigger and add the command action to it //var itemTapEventTrigger = new System.Windows.Interactivity.EventTrigger { EventName = "ItemTap" }; //itemTapEventTrigger.Actions.Add(itemTapInvokeCommandAction); //// attach the trigger to the control //triggers.Add(itemTapEventTrigger); //#endregion //添加DataRequested事件绑定 #region DataRequested var dataRequestedInvokeCommandAction = new InvokeCommandAction(); // create the command action and bind the command to it Binding dataRequestedParameterBinding = new Binding(); //设置源对象 dataRequestedParameterBinding.Source = item; //设置源属性 dataRequestedParameterBinding.Path = new PropertyPath("CurrentCategoryItem"); //dataRequestedParameterBinding.ElementName = listBox.Name; BindingOperations.SetBinding(dataRequestedInvokeCommandAction, InvokeCommandAction.CommandParameterProperty, dataRequestedParameterBinding); var dataRequestedEventBinding = new Binding { Path = new PropertyPath("CommandDataRequestedArticle") }; BindingOperations.SetBinding(dataRequestedInvokeCommandAction, InvokeCommandAction.CommandProperty, dataRequestedEventBinding); // create the event trigger and add the command action to it var dataRequestedEventTrigger = new System.Windows.Interactivity.EventTrigger { EventName = "DataRequested" }; dataRequestedEventTrigger.Actions.Add(dataRequestedInvokeCommandAction); // attach the trigger to the control triggers.Add(dataRequestedEventTrigger); #endregion //添加RefreshRequested事件绑定 #region RefreshRequested var refreshRequestedInvokeCommandAction = new InvokeCommandAction(); // create the command action and bind the command to it Binding refreshRequestedParameterBinding = new Binding(); //设置源对象 refreshRequestedParameterBinding.Source = item; //设置源属性 refreshRequestedParameterBinding.Path = new PropertyPath("CurrentCategoryItem"); //refreshRequestedParameterBinding.ElementName = listBox.Name; BindingOperations.SetBinding(refreshRequestedInvokeCommandAction, InvokeCommandAction.CommandParameterProperty, refreshRequestedParameterBinding); var refreshRequestedEventBinding = new Binding { Path = new PropertyPath("CommandRefreshRequestedArticle") }; BindingOperations.SetBinding(refreshRequestedInvokeCommandAction, InvokeCommandAction.CommandProperty, refreshRequestedEventBinding); // create the event trigger and add the command action to it var refreshRequestedEventTrigger = new System.Windows.Interactivity.EventTrigger { EventName = "RefreshRequested" }; refreshRequestedEventTrigger.Actions.Add(refreshRequestedInvokeCommandAction); // attach the trigger to the control triggers.Add(refreshRequestedEventTrigger); #endregion #region 下拉刷新完成后停止 PropertyChangedTrigger stopPullToRefreshTrigger = new PropertyChangedTrigger(); Binding pullToRefreshBinding = new Binding(); pullToRefreshBinding.Path = new PropertyPath("IsUIBusy"); //stopPullToRefreshTrigger.SetValue(PropertyChangedTrigger.BindingProperty, pullToRefreshBinding); //用下面这句不管用 BindingOperations.SetBinding(stopPullToRefreshTrigger, PropertyChangedTrigger.BindingProperty, pullToRefreshBinding); StopPullToRefreshLoadingAction stopPullToRefreshLoadingAction = new StopPullToRefreshLoadingAction(); Binding stopPullToRefreshTargetObjectBinding = new Binding(); stopPullToRefreshTargetObjectBinding.ElementName = listBox.Name; //stopPullToRefreshLoadingAction.SetValue(StopPullToRefreshLoadingAction.TargetObjectProperty, stopPullToRefreshTargetObjectBinding); //用下面这句不管用 BindingOperations.SetBinding(stopPullToRefreshLoadingAction, StopPullToRefreshLoadingAction.TargetObjectProperty, stopPullToRefreshTargetObjectBinding); stopPullToRefreshTrigger.Actions.Add(stopPullToRefreshLoadingAction); Binding pullToRefreshBinding2 = new Binding(); pullToRefreshBinding2.Path = new PropertyPath("IsUIBusy"); ComparisonCondition condition = new ComparisonCondition(); BindingOperations.SetBinding(condition, ComparisonCondition.LeftOperandProperty, pullToRefreshBinding2); condition.RightOperand = false; condition.Operator = ComparisonConditionType.Equal; ConditionalExpression expression = new ConditionalExpression(); expression.Conditions.Add(condition); expression.ForwardChaining = ForwardChaining.And; ConditionBehavior conditionBehavior = new ConditionBehavior(); conditionBehavior.Condition = expression; conditionBehavior.Attach(stopPullToRefreshTrigger); triggers.Add(stopPullToRefreshTrigger); #endregion grid.Children.Add(listBox); this.pivotMain.Items.Insert(i, pivot); } vm.IsUIBusy = false; } } }
这里主要是用到了在代码中设置绑定的方法,一般要先初始化一个Binding,设置其Path、ElementName、Source等属性,然后调用BindingOperations.SetBinding()方法来进行绑定。主要麻烦的部分是设置EventTrigger的绑定,上面的代码基本是xaml里的绑定用后台代码翻译了一遍。
但这种方式仍然不太好,下个版本再修改一下,改成全部Pivot项都用绑定的方式。
时间: 2024-10-20 11:14:14