如何在Windows Phone7中完全手动控制ListBoxItem的样式?

时间:2011-10-27 05:01:41

标签: windows-phone-7 listbox

我最近遇到了一个非常奇怪的问题。

我想自己控制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

谢谢, 闪闪发光

1 个答案:

答案 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);
    }