问题
I am currently using a custom Image object(a wrapper around <Image>
object) in a listView. The properties of my custom Image object don't change when a new listview item is visible(realised).
For instance if my listview (containing 30 items with different image urls and different text) has 3 items on the first scroll then the 10th item has the same image as the 1st item. The images repeat in the order [1-9][1-9][1-9].... But to my surprise the text is different in all 30 listViewItems.
On debugging I found that the setter for my image object gets called only for the first 9 items. Can somebody shed light on how other system components(System Image/TextBlock working fine) get new element values?
Code snippet of relevant class property:
public sealed partial class CustomImage : UserControl
{
public static readonly DependencyProperty ImageSourceStringProperty = DependencyProperty.Register("ImageSourceString", typeof(string), typeof(CustomImage), new PropertyMetadata(null, new PropertyChangedCallback(ImageSourceStringChanged)));
public string ImageSourceString
{
get { return (string)GetValue(ImageSourceStringProperty); }
set
{
//THIS NEVER GETS HIT FOR ITEMS AFTER 9th ITEM
SetValue(ImageSourceStringProperty, value);
//{More code here}
}
}
}
Xaml Usage
<ListView ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<custom:customImage x:Name="Img" ImageSourceString="{Binding ImgPath}"/>
<TextBlock Grid.Column="1" Text="{Binding Name}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Am I missing how it is supposed to work? In case something is not clear please let me know, I can clarify.
回答1:
The getter
and setter
of a dependency property are not guaranteed to be run, we'd better not put any other code in setter
. Please note following Caution in Custom dependency properties:
In all but exceptional circumstances, your wrapper implementations should perform only the GetValue and SetValue operations. Otherwise, you'll get different behavior when your property is set via XAML versus when it is set via code. For efficiency, the XAML parser bypasses wrappers when setting dependency properties; whenever possible, it uses the registry of dependency properties.
So instead of reacting in the setter
of your property, we can use ImageSourceStringChanged
method to act on the new value like following:
private static void ImageSourceStringChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
CustomImage currentImage = obj as CustomImage;
string oldValue = e.OldValue as string;
string newValue = e.NewValue as string;
MainPage.logMsg("ImageSource = " + newValue);
if (oldValue == null || !oldValue.Equals(newValue))
{
string path = newValue;
if (string.IsNullOrEmpty(path))
{
Uri imageFileUri = new Uri("ms-appx:///Assets/Images/failed.png");
currentImage.mainImage.ImageSource = new BitmapImage(imageFileUri);
}
else
{
Uri imageFileUri = null;
try
{
imageFileUri = new Uri(path);
}
catch
{
imageFileUri = new Uri("ms-appx:///Assets/Images/failed.png");
}
if (imageFileUri != null)
{
currentImage.mainImage.ImageSource = new BitmapImage(imageFileUri);
}
}
}
}
Besides, since the type of your DependencyProperty
is string
, you do not need to compare the OldValue
and NewValue
as the callback is only invoked if the value changed. See Property changed behavior for structures and enumerations.
If the type of a DependencyProperty is an enumeration or a structure, the may be invoked even if the internal values of the structure or the enumeration value did not change. This is different from a system primitive such as a string where it only is invoked if the value changed.
So the complete code of CustomImage
may like:
public sealed partial class CustomImage : UserControl
{
public static readonly DependencyProperty ImageSourceStringProperty = DependencyProperty.Register("ImageSourceString", typeof(string), typeof(CustomImage), new PropertyMetadata(null, new PropertyChangedCallback(ImageSourceStringChanged)));
public string ImageSourceString
{
get { return (string)GetValue(ImageSourceStringProperty); }
set
{
SetValue(ImageSourceStringProperty, value);
}
}
private static void ImageSourceStringChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
CustomImage currentImage = obj as CustomImage;
string path = e.NewValue as string;
MainPage.logMsg("ImageSource = " + path);
if (string.IsNullOrEmpty(path))
{
Uri imageFileUri = new Uri("ms-appx:///Assets/Images/failed.png");
currentImage.mainImage.ImageSource = new BitmapImage(imageFileUri);
}
else
{
Uri imageFileUri = null;
try
{
imageFileUri = new Uri(path);
}
catch
{
imageFileUri = new Uri("ms-appx:///Assets/Images/failed.png");
}
if (imageFileUri != null)
{
currentImage.mainImage.ImageSource = new BitmapImage(imageFileUri);
}
}
}
public CustomImage()
{
this.InitializeComponent();
}
}
来源:https://stackoverflow.com/questions/36088429/virtualisation-not-changing-properties-of-new-visible-items