我最近遇到了一个非常奇怪的问题。
我想自己控制ListBoxItem的渲染,以获得非常复杂的ListBox, 当我完成工作时,我发现第一次出现正是我想要的,但当我向下滚动ListBox时,我感到困惑和沮丧,看不到ListBoxItem的顺序根本没有。
现在我创建了一个新的空类似项目并进行了测试,结果是一样的。(在Windows Phone 7中,但在WPF应用程序中一切都很顺利。)
V1:
<ListBox x:Name="ListBoxTest">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Channel}" Width="1000"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
V1只是将属性绑定到特殊控件,这样一切都很顺利。 V2中的情况有所不同:
<ListBox x:Name="ListBoxTest">
<ListBox.ItemTemplate>
<DataTemplate>
<local:TestPanel Data="{Binding}" HoldedHeight="100"></local:TestPanel>
</DataTemplate>
</ListBox.ItemTemplate>
和DataTemplate中的TestPanel是:
public class TestPanel : StackPanel
{
private bool _beginLoaded;
private bool _isLoaded;
private ListBox _parentListBox;
public TestPanel()
: base()
{
this.Loaded += TestPanel_Loaded;
}
void TestPanel_Loaded(object sender, RoutedEventArgs e)
{
if (Data != null)
{
System.Diagnostics.Debug.WriteLine("ProgramsPanel_Loaded with {0}", Data.Channel);
}
else
{
System.Diagnostics.Debug.WriteLine("ProgramsPanel_Loaded with NULL data");
}
if (_beginLoaded || _isLoaded)
return;
_beginLoaded = true;
InitializeWidthParent();
this.Children.Clear();
Show(Data, HoldedWidth, HoldedHeight);
_isLoaded = true;
}
private void InitializeWidthParent()
{
_parentListBox = FindParent<listbox>(this);
if (_parentListBox != null)
{
HoldedWidth = _parentListBox.ActualWidth;
}
}
static T FindParent<t>(DependencyObject d) where T : DependencyObject
{
DependencyObject parent = d;
while (parent != null)
{
parent = VisualTreeHelper.GetParent(parent);
if (parent != null && (parent.GetType() == typeof(T)))
{
return parent as T;
}
}
return parent as T;
}
private void Show(TestDataItem data, double holdedwidth, double holdedheight)
{
if (data == null)
return;
if (!holdedwidth.IsValid() || !holdedheight.IsValid())
return;
#region Stand at Left Side (Channel Name and Image)
System.Diagnostics.Debug.WriteLine("Begin Show this Pannel of {0}", data.Channel);
TextBlock tb = new TextBlock() { FontSize = 32, Height=HoldedHeight,Text = data.Channel, HorizontalAlignment = HorizontalAlignment.Center };
this.Children.Add(tb);
#endregion
#region Right Side
#endregion
}
public double HoldedWidth
{
get;
set;
}
public double HoldedHeight
{
get;
set;
}
/// <summary>
/// The <see cref="Data" /> dependency property's name.
/// </summary>
public const string DataPropertyName = "Data";
/// <summary>
/// Gets or sets the value of the <see cref="Data" />
/// property. This is a dependency property.
/// </summary>
public TestDataItem Data
{
get
{
return (TestDataItem)GetValue(DataProperty);
}
set
{
SetValue(DataProperty, value);
}
}
/// <summary>
/// Identifies the <see cref="Data" /> dependency property.
/// </summary>
public static readonly DependencyProperty DataProperty = DependencyProperty.Register(
DataPropertyName,
typeof(TestDataItem),
typeof(TestPanel),
new PropertyMetadata(null, OnDataPropertyChanged));
private static void OnDataPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as TestPanel).OnDataPropertyChanged(e.OldValue as TestDataItem, e.NewValue as TestDataItem);
}
protected virtual void **OnDataPropertyChanged**(TestDataItem oldData, TestDataItem newData)
{
if (oldData != null)
{
Show(oldData, HoldedWidth, HoldedHeight);
System.Diagnostics.Debug.WriteLine("rebuild old pannel of {0}, ListBox count is {1}", oldData.Channel, _parentListBox.Items.Count);
}
string oldstr = oldData == null ? "NULL" : oldData.Channel;
string newstr = newData == null ? "NULL" : newData.Channel;
System.Diagnostics.Debug.WriteLine("old data is {0}, new data is {1}", oldstr, newstr);
}
}
public static partial class Extentsions
{
public static bool IsValid(this double width)
{
if (double.IsNaN(width)
|| Math.Abs(width) < 0.1)
{
return false;
}
return true;
}
}
public class TestDataItem
{
public string Channel
{
get;
set;
}
}
主页是:
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
ObservableCollection<testdataitem> source = new ObservableCollection<testdataitem>();
ListBoxTest.ItemsSource = source;
for (int i = 0; i < 10; i++)
{
source.Add(new TestDataItem() { Channel = i.ToString() });
}
}
}
那么为什么在我使用V2后ListBoxItems会出现乱序,我怎么能解决这个问题呢?
PS:输出信息是:
old data is NULL, new data is 0
old data is NULL, new data is 1
old data is NULL, new data is 2
old data is NULL, new data is 3
old data is NULL, new data is 4
old data is NULL, new data is 5
old data is NULL, new data is 6
old data is NULL, new data is 7
old data is NULL, new data is 8
old data is NULL, new data is 9
ProgramsPanel_Loaded with 0
Begin Show this Pannel of 0
ProgramsPanel_Loaded with 1
Begin Show this Pannel of 1
ProgramsPanel_Loaded with 2
Begin Show this Pannel of 2
ProgramsPanel_Loaded with 3
Begin Show this Pannel of 3
ProgramsPanel_Loaded with 4
Begin Show this Pannel of 4
ProgramsPanel_Loaded with 5
Begin Show this Pannel of 5
ProgramsPanel_Loaded with 6
Begin Show this Pannel of 6
ProgramsPanel_Loaded with 7
Begin Show this Pannel of 7
ProgramsPanel_Loaded with 8
Begin Show this Pannel of 8
ProgramsPanel_Loaded with 9
Begin Show this Pannel of 9
Begin Show this Pannel of 9
rebuild old pannel of 9, ListBox count is 10
old data is 9, new data is 0
ProgramsPanel_Loaded with 8
谢谢, 闪闪发光
答案 0 :(得分:0)
我猜这个问题是由列表框的“功能”引起的:UI Virtualization。
简单来说,UI虚拟化将帮助您减少UI内存,并仅渲染屏幕上可见的元素
因此,滚动列表框时,您的自定义测试面板将被回收并重复使用。
可能是我没有被清除,但您可以尝试通过使用stackpanel作为ListBox的ItemPanel来禁用列表框的UI虚拟化功能,以查看您的问题是否已解决。
您可以像这样定义列表框的ItemsPanelTemplate:
<ListBox>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
您还可以将propertychangedCallback添加到Data Property,并在此回调中重建panel元素。这将在您的面板被回收并且其数据被更改时调用。
/// <summary>
/// Identifies the <see cref="Data" /> dependency property.
/// </summary>
public static readonly DependencyProperty DataProperty = DependencyProperty.Register(
DataPropertyName,
typeof(TestDataItem),
typeof(TestPanel),
new PropertyMetadata(null, OnIsDataChanged));
protected static void OnIsDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//rebuild your panel here......
this.Children.Clear();
Show(Data, HoldedWidth, HoldedHeight);
}