当我点击这个简单的Win Phone 8应用程序(使用VS 2012 Pro构建 - 我所拥有的)上的“添加一些东西”按钮时,没有发生。为什么呢?
此示例代码的回购在bitbucket.org上: TestItemsControlInWinPhone8App
MainPage.xaml
包含:
<phone:PhoneApplicationPage
x:Class="TestItemsControlInWinPhone8App.MainPage"
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"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">
<!--LayoutRoot is the root grid where all page content is placed-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel contains the name of the application and page title-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock Text="MY APPLICATION" Style="{StaticResource PhoneTextNormalStyle}" Margin="12,0"/>
<TextBlock Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button x:Name="AddSomeThing"
Content="Add Some Thing"
Grid.Row="0"
Click="AddSomeThing_Click"/>
<ItemsControl x:Name="LotsOfThingsItemsControl"
Grid.Row="1"
ItemsSource="{Binding Mode=OneWay}"
FontSize="{StaticResource PhoneFontSizeSmall}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Height="Auto" Width="Auto"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Background="Orange">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Path=Id, Mode=OneWay}"/>
<TextBlock Grid.Row="1"
Text="------------------------"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
</Grid>
</Grid>
</phone:PhoneApplicationPage>
请注意ItemsControl ItemsSource="{Binding Path=Things}
也仅仅是ItemsControl ItemsSource="{Binding}"
。
两者都有相同的结果;显示前五个Thing
个对象,然后点击“添加一些内容”按钮会将另一个Thing
添加到LotsOfThings
。
DataTemplate
TextBlock
的内容实际上显示了前5个Thing
个对象。
但是点击该按钮不会更新显示,该显示仅显示原始的5 Thing
个对象。
背后的代码(MainPage.xaml.cs
)读取:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using TestItemsControlInWinPhone8App.Resources;
namespace TestItemsControlInWinPhone8App
{
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
this.DataContext = new LotsOfThings(5);
}
private void AddSomeThing_Click(object sender, RoutedEventArgs e)
{
LotsOfThings lot = this.DataContext as LotsOfThings;
lot.Add(new Thing());
}
}
}
请注意,在Page
构造函数this.DataContext = new LotsOfThings(5);
中,当Page
首次显示时,会显示5个Thing
个对象。
要清楚,什么不起作用是AddSomeThing_click()
按钮处理程序的后一次调用会将另一个Thing
添加到LotsOfThings
但仅显示原始的5 Thing
个对象;仅此而已,根据调试器,Thing
上存在更多LotsOfThings
个对象。
我注意到使用调试器时,只要调用OnPropertyChanged(...)
,handler
就是null
。这显然很重要,但我不知道为什么会发生这种情况,所以我可以在网上搜索所有补救措施。
为什么吗
Thing.cs
的内容:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestItemsControlInWinPhone8App
{
public class Thing : INotifyPropertyChanged
{
private string _Id = Guid.NewGuid().ToString();
public string Id
{
get
{
return _Id;
}
set { }
}
#region Constructor
public Thing()
{
this.OnPropertyChanged( "Id");
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string pPropertyName)
{
System.ComponentModel.PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(pPropertyName));
}
}
#endregion
}
}
LotsOfThings.cs
的内容:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TestItemsControlInWinPhone8App
{
class LotsOfThings : INotifyPropertyChanged, IList<Thing>
{
private List<Thing> _things = new List<Thing>();
public List<Thing> Things
{
get {
return _things;
}
set { }
}
public LotsOfThings( int pNumberOfThings)
{
for( int x = 0; x < pNumberOfThings; x++){
this.Add( new Thing());
}
OnPropertyChanged("Things");
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string pName)
{
System.ComponentModel.PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(pName));
}
}
#endregion
#region IList<T> methods
public int IndexOf(Thing item)
{
return _things.IndexOf(item);
}
public void Insert(int index, Thing item)
{
_things.Insert(index, item);
}
public void RemoveAt(int index)
{
_things.RemoveAt(index);
}
public Thing this[int index]
{
get
{
return _things[index];
}
set
{
_things[index] = value;
}
}
public void Add(Thing item)
{
_things.Add(item);
OnPropertyChanged("Things");
}
public void Clear()
{
_things.Clear();
}
public bool Contains(Thing item)
{
return _things.Contains(item);
}
public void CopyTo(Thing[] array, int arrayIndex)
{
_things.CopyTo(array, arrayIndex);
}
public int Count
{
get { return _things.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(Thing item)
{
return _things.Remove(item);
}
public IEnumerator<Thing> GetEnumerator()
{
return _things.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _things.GetEnumerator();
}
#endregion
}
}
如果您只需要下载应用程序或使用更好的界面查看它,您可以在此处找到它: TestItemsControlInWinPhone8App
谢谢。
PS。我已阅读并且,我认为,遵循我在Stackoverflow和网络上的其他地方可以找到的关于传递到OnPropertyChanged()
方法的空处理程序和我可以找到的ItemsControl
的使用的所有建议。
答案 0 :(得分:3)
_things
需要ObservableCollection<Thing>
。 List<T>
未实施INotifyCollectionChanged
,因此在内容发生变化时不会发出通知。 ObservableCollection<Thing>
会这样做,这将使UI知道何时需要将项添加到列表中。
简单,容易,标准的做事方式是将ObservableCollection
作为财产公开。如果您用新的集合替换整个集合,请引发PropertyChanged("Things")
;添加/删除项目时,ObservableCollection
将引发相应的事件,而无需您执行任何操作。经验丰富的WPF人员阅读您的代码将知道他们正在看什么。
为了让它以您的方式运作,您必须在改变OnPropertyChanged("Things")
集合的方法中调用Things
;我还没有对它进行测试,但我认为它应该有效(原因可能不是Things
返回的实际集合对象没有改变;控件可能会看到并选择不更新;但正如我所说,我还没有测试过那个)。然后你可以将Things
绑定到控件上的ItemsSource
,也许它应该可以工作。但是,你可以让其他课程改变Things
,因为它是公开的。试图追平所有松散的目标将是一团糟。更容易使用ObservableCollection
。
如果您想将LotsOfThings
本身绑定到ItemsSource
,则必须在INotifyCollectionChanged
上实施LotsOfThings
,这将成为重写的真正麻烦这一切都是手工制作的,而且我不确定它是什么给你买的。你可以让LotsOfThings
成为ObservableCollection<Thing>
的子类 - 免费提供完整且防弹的INotifyCollectionChanged
实现。
答案 1 :(得分:1)
Ed Plunkett让我走上正确的道路,但答案有点复杂,所以我在这个答案中列出了我所学到的知识。
首先,我本可以使用ObservableCollection<T>
- Ed是对的。但我会丢失一些我想要的IList<T>
功能。此外,我试图遵循XAML Deep Dive ... (min 40-49)中的做法,并且他们没有使用ObservableCollection<T>
。
原来我错误地使用了INotifyPropertyChanged
而不是INotifyCollectionChanged
。第二个接口有一个稍微复杂的处理程序,记录在这个关于calling OnCollectionChanged
的Stackoverflow问题的答案中。
在问这个问题之前我的研究发现了一堆方法来获得一个null事件处理程序。一种是使用拼写错误的属性名称调用处理程序(例如OnPropertyChanged("thing")
,当你应该使用OnPropertyChanged("Thing")
时,因为这是属性实际调用的 - 假设你正在处理属性而不是集合。另一种方法是获取null事件处理程序是为了不将正确的对象绑定到正确的内容或容器控件。在这里,看看"stack overflow C# why is my handler null"。
最后,为了解决这个问题的核心,我在Ed的提示方面做了一些研究,我对difference between List<T>
, ObservableCollection<T>
and INotifyPropertyChanged
更加熟悉并发现了优秀的页面。
我希望有所帮助。谢谢艾德;我所有的选票都给了你。
P.S。我更新了测试代码的存储库以获得修复,git tag
- 更新了original version as broken and the fixed version as fixed。