将可观察集合与Pivot控件绑定的MVVM方法(Windows Phone 8)

4

我是新手WP8和MVVM。我创建了一个WP8应用程序,一旦用户登录,就会请求各种数据。我无法动态创建我的Pivot标题,我不知道这是因为我在绑定中做了什么,INotifyPropertyChanged,两者都还是其他原因!

到目前为止,我已经完成了以下工作:

我定义了一个全局MainViewModel,在App.cs中存储所有在登录时返回的数据。

一旦登录成功并将数据加载到MainViewModel中,我将其重定向到一个测试页面,其中包含一个Pivot控件,我正在尝试动态创建Pivot项目。

这是我的测试页面即MainPivotPage.xaml的xaml,我的MainPivotViewModel被初始化,因为它被定义为本地资源,并设置为Pivot控件的数据上下文,我不知道我是否做对了,但我将“Name”属性分配给PivotItem的Header,这是存储在我的可观察集合Pivots中的对象。 Name属性是我在称为Pivot的类中拥有的2个属性之一,该类包含PivotId和Name。

<phone:PhoneApplicationPage
    x:Class="TestApp.Views.MainPivotPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:viewModel="clr-namespace:TestApp.ViewModels"
    mc:Ignorable="d"
    FontFamily="{StaticResource PhoneFontFamilyNormal}"
    FontSize="{StaticResource PhoneFontSizeNormal}"
    Foreground="{StaticResource PhoneForegroundBrush}"
    SupportedOrientations="Portrait"  Orientation="Portrait"
    shell:SystemTray.IsVisible="True" Loaded="PhoneApplicationPage_Loaded">
    <!--LayoutRoot is the root grid where all page content is placed-->

    <phone:PhoneApplicationPage.Resources>
        <viewModel:MainPivotViewModel x:Key="MainPivotViewModel" />
    </phone:PhoneApplicationPage.Resources>

    <Grid x:Name="LayoutRoot" Background="Transparent">
        <!--Pivot Control-->
        <phone:Pivot Title="My Search Options" x:Name="MainPivots" ItemsSource="{Binding Pivots}" DataContext="{StaticResource MainPivotViewModel}">
            <phone:PivotItem Header="{Binding Name}">
                <!--<Grid/>-->
            </phone:PivotItem>
        </phone:Pivot>
    </Grid>
</phone:PhoneApplicationPage>

当MainPivotViewModel被创建时,我将Pivots可观察集合设置为存储在我的MainViewModel中的相同可观察集合,该集合包含登录返回的所有数据。正如您所看到的,我将其分配给属性而不是内部变量,以确保它将触发INotifyPropertyChanged(好吧...,我想)

public class MainPivotViewModel : BaseViewModel
{
    private ObservableCollection<Pivot> _pivots = null;

    public MainPivotViewModel()
    {
        Pivots = App.MainViewModel.Pivots;            
    }

    public ObservableCollection<Pivot> Pivots
    {
        get
        {
            return _pivots;
        }

        set
        {
            if (_pivots != value) this.SetProperty(ref this._pivots, value);
        }
    }
}

我使用Base类中包含的SetProperty函数,它用于生成INotifyPropertyChanged事件,并且允许我在每次需要INotifyPropertyChanged事件时不必设置属性名称。
这是我的BaseView代码:
public class BaseViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
    {
        if (object.Equals(storage, value)) return false;

        storage = value;
        this.OnPropertyChanged(propertyName);
        return true;
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var eventHandler = this.PropertyChanged;
        if (eventHandler != null)
        {
            eventHandler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

我的Pivot类如下所示:
public class Pivot: BaseModel
{
    private int _pivotId;
    private string _name = string.Empty;

    public Pivot()
    {

    }

    public Pivot(int pivotId, string name)
    {
        PivotId = pivodId;
        Name = name;
    }

    public int PivotId
    {
        get { return _pivotId; }
        set { if (_pivotId != value) this.SetProperty(ref this._pivotId, value); }
    }

    public string Name
    {
        get { return _name; }
        set { if (_name != value) this.SetProperty(ref this._name, value); }
    }
}

你可能会注意到这个类继承自BaseModel。这段代码和BaseViewModel中的完全一样,但我想保持两者分开。我甚至不确定我的Pivot类是否需要这个,但我试过不同的场景,现在还留着它。
我不知道我哪里做错了,但无论我尝试什么,都无法将“Name”属性显示为标题文本。我很确定MainPivotViewModel在被分配为本地资源时已经初始化了,因为它正确地调用了我的构造函数,而我的可观察集合也被初始化了,但就是这样,它什么也没有显示。
另一件我注意到的事情是,当我在BaseViewModel类的OnPropertyChanged方法的“Set”中放置断点时,无论如何eventHandler始终为空,这应该不是我想要的情况,但我看不出我做错了什么。
我在stackoverflow和其他地方阅读了无数篇文章,但就是找不出我做错了什么。有人有什么想法吗?
谢谢。

你在页面的任何地方设置了 DataContext 吗? - Paul Annetts
是的,在xaml文件中设置了它。只是它不可见,但如果你滚动一下就会发现已经设置了。 - Thierry
你有一个 PivotObservableCollection。这些是你自己的类吗?如果是,它们长什么样子? - Matt Lacey
嗨,马特,我刚刚在原始帖子中添加了这个类。 - Thierry
当您使用ObservableCollection时,需要更改集合的内容而不是集合本身。如果(_pivots != value),则此.SetProperty(ref this._pivots, value)会更改集合,所有绑定到先前集合的绑定都将丢失。 - Pedro.The.Kid
3个回答

3
问题已解决!!!
一直以来我的代码都是正确的,但是XAML不是!我想这就是一个陡峭而痛苦的学习曲线吧!总之,在Stackoverflow上找到了一篇文章后我终于找到了解决方法,这篇文章基本上告诉我,我写XAML的方式是不正确的。说实话,我不明白为什么它不能按照定义的方式工作,但是简单来说,我必须使用HeaderTemplate和ItemTemplate来正确显示数据,当与ViewModel绑定时!
以下是该文章链接:Databound pivot is not loading the first PivotItem in Windows Phone 8

2
抱歉,你的回答和代码都是错误的。
错误1:
    set
    {
        if (_pivots != value) this.SetProperty(ref this._pivots, value);
    }

这里无论你改变属性还是变量,绑定都会丢失。

错误2:所有从ItemsControl派生的UIElements都忽略了INotifyPropertyChanged,因为它不更新ItemsSource,只更新DataContext。

工作示例

    public ObservableCollection<string> LstLog { get; set; }
    private ObservableCollection<string> _lstContent = new ObservableCollection<string>();
    public ObservableCollection<string> LstContent
    {
        get
        {
            LstLog.Add("get");
            return _lstContent;
        }
        set
        {
            LstLog.Add("set");
            _lstContent = value;
        }
    }
    public MainWindow()
    {
        LstLog = new ObservableCollection<string>();

        InitializeComponent();
        this.DataContext = this;
    }

    private void Add_Click(object sender, RoutedEventArgs e)
    {
        LstContent.Add("Value added");
    }

    private void New_Click(object sender, RoutedEventArgs e)
    {
        _lstContent = new ObservableCollection<string>();
    }

    private void NewBind_Click(object sender, RoutedEventArgs e)
    {
        _lstContent = new ObservableCollection<string>();
        listObj.ItemsSource = _lstContent;
    }

    private void NewProp_Click(object sender, RoutedEventArgs e)
    {
        LstContent = new ObservableCollection<string>();
    }

    private void NewPropBind_Click(object sender, RoutedEventArgs e)
    {
        LstContent = new ObservableCollection<string>();
        listObj.ItemsSource = LstContent;
    }

和用户界面 (UI)

<Grid DataContext="{Binding}">
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
        <RowDefinition Height="25" />
    </Grid.RowDefinitions>

    <ItemsControl Grid.Row="0" Name="logObj" ItemsSource="{Binding Path=LstLog}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding}"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    <ItemsControl Grid.Row="1" Name="listObj" ItemsSource="{Binding Path=LstContent}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding}"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    <StackPanel Grid.Row="2" Orientation="Horizontal">
        <Button Name="Add" Content="Add" Click="Add_Click"/>
        <Button Name="New" Content="New" Click="New_Click"/>
        <Button Name="NewBind" Content="New Bind" Click="NewBind_Click"/>
        <Button Name="NewProp" Content="New Prop" Click="NewProp_Click"/>
        <Button Name="NewPropBind" Content="New Prop Bind" Click="NewPropBind_Click"/>
    </StackPanel>
</Grid>

LstLog 是用来查看事件列表的,如果你点击 add 它将更新两个列表,但如果点击 New 或者 New Prop 绑定将会丢失,直到你像在 New Bind 或者 New Prop Bind 中更新它。

希望这样能澄清 XAML 列表事件的循环问题。

PS:这是在 WPF 中,但在 WP8 和 Windows Store 应用程序中也是一样的。


1
这一切在我看来都很好,所以我认为当构造函数被调用时,App.MainViewModel.Pivots 为空或者为空集合(因此 Pivot 控件为空),并且您最终在 MainPivotViewModel 实例化后创建了 Pivots 的新实例。
您是否尝试在 MainPivotViewModel.Pivots 的 getter 中设置断点以确认是否有一些项目?

我已经在我的代码中设置了各种断点,可以清楚地看到:<br/><br/> a) MainPivotViewModel被初始化,并且仅在我保留本地资源xaml定义时才会初始化。 <br/><br/> b) 可观察集合Pivots被设置为App.MainViewModel.Pivots <br/><br/> c) App.MainViewModel.Pivots肯定包含数据。这个特定的集合包含3个返回的枢轴。<br><br/> 如上所述,在通过代码时,我注意到var eventHandler = this.PropertyChanged;总是返回null。可能是这样,但为什么呢? - Thierry
我注意到的另一件事是,当我逐步执行代码时,我的DataContext是通过xaml设置的,但是当我查看代码后台中的DataContext时,它为null?为什么会这样?此外,某些东西必须被触发,因为虽然它没有显示标题的名称,但它在pivot的查看部分和标题中显示了MyTestApp.Models.Pivot。奇怪!就像它正在执行一个object.ToString()而不是实际尝试显示属性...根据上面的xaml,您认为它是否正确? - Thierry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接