I met a very strange problem recently.
I want to control the ListBoxItem’s render by myself for a very complicated ListBox,
when i finish the work , I found that the first appearance is exactly what i wanna, but when i scroll down the ListBox, I was confused and frustrated to see the orders of ListBoxItem is not in order at all.
Now I created a new empty similiar project and tested ,the result is same.( In Windows Phone 7 is , But in WPF application everything is well.)
V1:
<ListBox x:Name="ListBoxTest">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Channel}" Width="1000"></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
V1 is just binding the property to special control, in this way ,everything is well .
and things are different in V2:
<ListBox x:Name="ListBoxTest">
<ListBox.ItemTemplate>
<DataTemplate>
<local:TestPanel Data="{Binding}" HoldedHeight="100"></local:TestPanel>
</DataTemplate>
</ListBox.ItemTemplate>
and the TestPanel in DataTemplate is :
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;
}
}
the main page is :
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() });
}
}
}
so why after I use V2 the ListBoxItems will disorderly appear , and how could I solve this problem?
PS: the output information is:
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
Thanks,
Sparkling
I guess the problem is caused by the “feature” of listbox: UI Virtualization.
Simply speaking, UI Virtualization will help you reduce the UI memory and only render the elements visible on the screen.
So your custom testpanel will be recycled and reused when you scroll the listbox.
May be I am not cleared, but you can just try to disable the UI Virtualization feature of the listbox by using stackpanel as the ListBox’s ItemPanel to see if your problem is resolved.
You can define your listbox’s ItemsPanelTemplate like this:
Also you can add a propertychangedCallback to your Data Property, and rebuild your panel element in this callback. This will call when your panel is recycled and its Data is changed.