问题
I'm new to WP8 & MVVM. I created wp8 app which requests various bits of data once a user has logged in. I just can't get my pivots header to get created dynamically and I don't know if it is because I'm doing something in the binding, the INotifyPropertyChanged, both or something else!!
Here is what I have done so far:
I've got a global MainViewModel defined in App.cs which will stored all the data returned at login time.
Once the login is successful and the data has been loaded into the MainViewModel, I redirect this to a test page which contains a Pivot Control and I'm trying to create Pivot Items dynamically.
This is the xaml for my test page i.e. MainPivotPage.xaml and my MainPivotViewModel is initialized as it is defined as a local resource and is set as the datacontext for the Pivot control and I don't know if I'm doing this right but I'm assigning the "Name" property to the Header of the PivotItem which are the object stored in my observable collection Pivots. The property Name is one of the 2 properties I have in a Class called Pivot which contains PivotId and 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>
When the MainPivotViewModel is created, I set the Pivots observable collection to the same observable collection stored in my MainViewModel which contains all the data returned at logon. As you can see I assign it to the property rather than the internal variable to ensure that it will trigger the INotifyPropertyChanged (well..., I think)
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);
}
}
}
I use the SetProperty function which is contained in my base class and is used to generate the INotifyPropertyChanged event and allows me to do so without having the set the property name every time I need to the INotifyPropertyChanged event.
This is the code for my 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));
}
}
}
My Pivot class looks like this:
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); }
}
}
You may notice that this one is inheriting from BaseModel. This is exactly the same code as in BaseViewModel but I wanted to keep the two separate. I'm not event sure this is needed on my Pivot class, but I was trying different scenario and left it in for now.
I don't know what I'm doing wrong, but no matter what I try I can't get the "Name" property to be displayed as the Header's text. I'm pretty sure the MainPivotViewModel is initialized when it is assigned as a local resource as I it's calling my constructor correctly which is then initializing my observable collection but that's as far as it goes.
But it displays absolutely nothing!
Another thing I've noticed is when I put breakpoints in the "Set" in OnPropertyChanged method in the BaseViewModel class, the eventHandler is always null, no matter what which I assume should not be the case, but I can't see what I'm doing wrong.
I've numerous articles on stackoverflow and others and I just can't see what I'm doing wrong? Anyone got any ideas?
Thanks.
回答1:
Problem resolved!!!
My code was right all along but the XAML wasn't!
Steep and painful learning curve I guess! Anyway, I found a resolution after finding an article on stackoverflow which basically showed me that the way I wrote the xaml was just not appropriate.
I'll be honest, I don't understand why this doesn't work the way it was defined but in short, I have to use HeaderTemplate and ItemTemplate in order to display the data correctly when binded to a ViewModel!
Here is the post: Databound pivot is not loading the first PivotItem in Windows Phone 8
回答2:
No you answer is wrong and your code is wrong.
Error 1:
set
{
if (_pivots != value) this.SetProperty(ref this._pivots, value);
}
in here it dos not matter if you change the property or the variable the binding will be lost.
Error 2: All UIElements derived from ItemsControl ignore the INotifyPropertyChanged because it does not update the ItemsSource just the DataContext.
Working Example
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;
}
and the 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>
the LstLog
is just to see the event list, if you click add
it will update the two list but if click the New
or the New Prop
the binding is lost until you update it like in the New Bind
or New Prop Bind
Hope this will clarify the XAML List event recurrent problem.
PS: this is in WPF but workes the same in WP8 and windows store app.
回答3:
This all looks fine to me, so I'm thinking that App.MainViewModel.Pivots is null or empty when the Constructor is called (therefore the Pivot control is empty), and that you end up creating a new instance of Pivots in your MainViewModel after the MainPivotViewModel is instantiated.
Have you tried putting a break-point in the getter for MainPivotViewModel.Pivots to confirm that there are some items?
来源:https://stackoverflow.com/questions/15651419/binding-pivot-control-with-observable-collection-mvvm-windows-phone-8