I know I know I shouldn't do this but I am so new to WPF and BitmapEffects is less daunting than Effects. SO - I have added a border with bitmapeffects to my window and - no bitmap effects. The border is there but not effects are applying. Any clues?
<Border BorderThickness="10" BorderBrush="Red" Padding="10,10,10,10" CornerRadius="5" Margin="5,5,5,5">
<Border.BitmapEffect>
<BevelBitmapEffect BevelWidth="6" LightAngle="120" Relief="1" EdgeProfile="BulgedUp"/>
</Border.BitmapEffect>
</Border>
It's just not going to work, you have to use Effects instead:
[Obsolete("BitmapEffects are deprecated and no longer function. Consider using Effects where appropriate instead.")]
public BitmapEffect BitmapEffect
{
get
{
return (BitmapEffect) this.GetValue(UIElement.BitmapEffectProperty);
}
set
{
this.SetValue(UIElement.BitmapEffectProperty, (object) value);
}
}
Related
Why does the BorderThickness changes the rendered CornerRadius?
Also, what is the design reasoning/philosophy behind it?
I just cannot understand it, maybe I'm missing something.
<Border Width="300"
Height="300"
Background="Red"
BorderBrush="Blue"
CornerRadius="5"
BorderThickness="50" />
<Border Width="300"
Height="300"
Background="Red"
BorderBrush="Blue"
CornerRadius="5"
BorderThickness="10" />
I see Rectangle has the same behavior.
Is there any element in WPF or WinUI which I can use to draw an exact radius so I can respect the designer's requirement?
Besides Path with custom points, I do not see any other way.
The issue with Path is I need to recompute the points myself when width/height changes which will hurt performance.
EDIT: Trying to tweak the corner radius so that it can match the design spec turns out to be impossible.
For example, let's assume the designer wants a Border with CornerRadius=5 and BorderThickness = 30.
In the image below, the top Border shows what an actual CornerRadius=5 looks like.
In the bottom Border, I try to meet the design spec. I set the BorderThicikness=30 and I tweak the CornerRadius to something very small so that it looks close to the corner radius of the Border above.
But the CornerRadius remains quite big even for a very small value 0.0000002:
<Border Width="100"
Height="100"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Background="Red"
BorderThickness="0"
CornerRadius="5"/>
<Border Width="100"
Height="100"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Background="Red"
BorderBrush="Blue"
BorderThickness="30"
CornerRadius="0.0000002" />
EDIT #2:
So that it's even more obvious how big the corner radius of the bottom Border is compared to the top one:
Why does the BorderThickness changes the rendered CornerRadius?
Looking at Border's method OnRender, there is a Pen which aim at drawing a rounded rectangle if CornerRadius property is set... the pen has Thickness Property, Pen's thickness = Border's thickness.
This is the flow of execution inside OnRender method when both CornerRadius and BorderThickness are set (in case of uniform thickness and uniform radius)..
pen1 = new Pen();
pen1.Brush = borderBrush;
pen1.Thickness = borderThickness.Left;
double topLeft1 = cornerRadius.TopLeft;
double num = pen1.Thickness * 0.5;
Rect rectangle = new Rect(new Point(num, num), new Point(this.RenderSize.Width - num, this.RenderSize.Height - num));
dc.DrawRoundedRectangle((Brush) null, pen1, rectangle, topLeft1, topLeft1);
So there is a Thickness-Radius trade-off that can not be avoided, And probably the only way to deal with it is to set a base border (the border that meets the designer's guidelines) and for the other border you would increase its radius if its thickness is less than the base border's thickness!
Update
Note that the dimensions of the border (Width and Height) is the third factor in the equation (see DrawRoundedRectangle call).
Imagine you are about to draw a rectangle of size 100x100 using a pen with thickness = 30.. You start drawing from the middle of the top line and moving the pen towards the left (image below).. Now, you will start shifting the pen early before reaching the corner because you are not allowed to get out of the 100x100, and that will result in a big curve so the value of the radius will not take affect in this case because the Width and Height has higher priority that the CornerRadius.
So to let a very small radius like 0.0000002 take affect, you have to enlarge the dimensions of the rectangle to a size that a pen with thickness = 30 will look small enough to reache near the corners.
Here is the first xaml code after tweaking radius of the second border (set it to 20) to make it looks like the first border..
And to make the first border looks like the second border, you can change its code to this
<Grid>
<Border
Width="300"
Height="300"
BorderBrush="Blue"
BorderThickness="10"
CornerRadius="5" />
<Border
Width="300"
Height="300"
Background="Red"
BorderBrush="Blue"
BorderThickness="50"
CornerRadius="5" />
</Grid>
I'm trying to make a custom progress bar in WPF that has two values (the second is always equal to or higher than the first). The basic bar works ok like so:
<wpft:ClippingBorder BorderBrush="{StaticResource Border}"
Background="{StaticResource Background}"
BorderThickness="1" CornerRadius="4">
<Grid Margin="-1" x:Name="Bars">
<Border BorderBrush="{StaticResource Border}"
Background="{Binding Value2Brush}"
BorderThickness="1" CornerRadius="4"
HorizontalAlignment="Left"
Width="{Binding Value2Width}" />
<Border BorderBrush="{StaticResource Border}"
Background="{Binding Value1Brush}"
BorderThickness="1" CornerRadius="4"
HorizontalAlignment="Left"
Width="{Binding Value1Width}" />
</Grid>
</wpft:ClippingBorder>
(Where ClippingBorder is this. It's used to prevent glitching at the outer corners when the values are near 0.)
The net result is a nice rounded display:
Zoomed view, to more clearly show the rounded corners:
In particular note that both of the inner bars share the same outer border and their right edge curves to the left, just like the outer border.
This works because it draws the longer bar first, then the shorter one on top of it. However, it only works reliably when the brushes are fully opaque -- in particular if Value1Brush is partially transparent then some of Value2Brush will show through it, which I don't want.
Ideally, I want the longer bar to only draw that portion of itself that extends beyond the shorter bar -- or equivalently, to set the clipping/opacity mask of the longer bar to be transparent in the area where the shorter bar is drawn.
But I'm not sure how to do that without losing the rounded corners.
This is not a general solution, unfortunately, but it seems to work for this case. I had to give an x:Name to each of the internal borders and then put this in the code behind:
Constructor:
DependencyPropertyDescriptor.FromProperty(ActualWidthProperty, typeof(Border))
.AddValueChanged(OuterBar, Child_ActualWidthChanged);
DependencyPropertyDescriptor.FromProperty(ActualWidthProperty, typeof(Border))
.AddValueChanged(InnerBar, Child_ActualWidthChanged);
Handler:
private void Child_ActualWidthChanged(object sender, EventArgs e)
{
var outerRect = new Rect(OuterBar.RenderSize);
outerRect.Inflate(5, 5);
var outer = new RectangleGeometry(outerRect);
var corner = InnerBar.CornerRadius.TopLeft;
var inner = new RectangleGeometry(new Rect(InnerBar.RenderSize), corner, corner);
OuterBar.Clip = new GeometryGroup()
{
Children = { outer, inner }
};
}
The basic idea is to start with a rectangle slightly larger than what the outer bar wants to draw, and then add a rectangle that exactly matches what the inner bar wants to draw -- this clips it out of the geometry. The whole is then used as a clip region for the outer bar so that it can't draw inside the inner bar's region.
I originally tried to do this in XAML with the following, but it didn't work (the converter was not called when the width changed); I'm not sure why, but just for posterity:
<Border.Clip>
<GeometryGroup>
<RectangleGeometry Rect="{Binding ElementName=OuterBar, Path=RenderSize,
Converter={StaticResource BoundsConverter}, ConverterParameter=5.0}" />
<RectangleGeometry Rect="{Binding ElementName=InnerBar, Path=RenderSize,
Converter={StaticResource BoundsConverter}}" RadiusX="4" RadiusY="4" />
</GeometryGroup>
</Border.Clip>
(Here the converter would take the RenderSize and make a Rect, with optional inflation, similar to the code above.)
I would try to use grid's columns. Putting two columns in the grid, first on with width="auto" second as "*".Put short border in the first column the other to the second column. When you change the width of your border column will resize accordingly to your border's width.
I've come across a very strange "feature" of the ListView. Hopefully someone can help me out here.
When you create a ListView on your Window, it comes with a default spacing between the border and the content. I guess it's a padding of 2 (left and right) if you look at the Snoop information. The ListBoxChrome (part of the ListView) is in my case 363px wide. The ScrollViewer inside it is 359px. There's nothing set on both these controls. Even a new project with a simple ListView has this issue.
One workaround is to give the header cells a padding of -2, but for some reason the headers won't fill until the right and leaves me with a wider gap at the right.
Someone here to help me out?
Some screenshots:
http://i.imgur.com/AKbDfwQ.png
http://i.imgur.com/pQtqMJ4.png
It's a combination of BorderThickness and Padding from the Border element (each contribute to 2px. 1px from left and 1px from right).
You can set
<ListView BorderThickness="0">
and loose 2px, however Padding atleast on Windows-8 is set directly on the Border control in the default Template and would not take any effect if set directly on the ListView
extract from default Style for ListView
<ControlTemplate TargetType="{x:Type ListView}">
<Border x:Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="1"
SnapsToDevicePixels="true">
simplest option is to provide a custom Style where you tweak that Padding value to 0. You can also choose to use Behavior's and get a reference to the Border control and override padding in code.
If you choose the option of code-behind to override Padding a very rough way to set the padding could be like:
public MainWindow()
{
InitializeComponent();
Loaded += (sender, args) => {
var border = (Border)lv.Template.FindName("Bd", lv);
border.Padding = new Thickness(0);
};
}
and your xaml:
<ListView x:Name="lv"
BorderThickness="0">
How do I configure a TextBox control to automatically resize itself vertically when text no longer fits on one line?
For example, in the following XAML:
<DockPanel LastChildFill="True" Margin="0,0,0,0">
<Border Name="dataGridHeader"
DataContext="{Binding Descriptor.Filter}"
DockPanel.Dock="Top"
BorderThickness="1"
Style="{StaticResource ChamelionBorder}">
<Border
Padding="5"
BorderThickness="1,1,0,0"
BorderBrush="{DynamicResource {ComponentResourceKey TypeInTargetAssembly=dc:NavigationPane,
ResourceId={x:Static dc:NavigationPaneColors.NavPaneTitleBorder}}}">
<StackPanel Orientation="Horizontal">
<TextBlock
Name="DataGridTitle"
FontSize="14"
FontWeight="Bold"
Foreground="{DynamicResource {ComponentResourceKey
TypeInTargetAssembly=dc:NavigationPane,
ResourceId={x:Static dc:NavigationPaneColors.NavPaneTitleForeground}}}"/>
<StackPanel Margin="5,0" Orientation="Horizontal"
Visibility="{Binding IsFilterEnabled, FallbackValue=Collapsed, Mode=OneWay, Converter={StaticResource BooleanToVisibility}}"
IsEnabled="{Binding IsFilterEnabled, FallbackValue=false}" >
<TextBlock />
<TextBox
Name="VerticallyExpandMe"
Padding="0, 0, 0, 0"
Margin="10,2,10,-1"
AcceptsReturn="True"
VerticalAlignment="Center"
Text="{Binding QueryString}"
Foreground="{DynamicResource {ComponentResourceKey
TypeInTargetAssembly=dc:NavigationPane,
ResourceId={x:Static dc:NavigationPaneColors.NavPaneTitleForeground}}}">
</TextBox>
</StackPanel>
</StackPanel>
</Border>
</Border>
</DockPanel>
The TextBox control named "VerticallyExpandMe" needs to automatically expand vertically when the text bound to it does not fit on one line. With AcceptsReturn set to true, TextBox expands vertically if I press enter within it, but I want it do do this automatically.
Although Andre Luus's suggestion is basically correct, it won't actually work here, because your layout will defeat text wrapping. I'll explain why.
Fundamentally, the problem is this: text wrapping only does anything when an element's width is constrained, but your TextBox has unconstrained width because it's a descendant of a horizontal StackPanel. (Well, two horizontal stack panels. Possibly more, depending on the context from which you took your example.) Since the width is unconstrained, the TextBox has no idea when it is supposed to start wrapping, and so it will never wrap, even if you enable wrapping. You need to do two things: constrain its width and enable wrapping.
Here's a more detailed explanation.
Your example contains a lot of detail irrelevant to the problem. Here's a version I've trimmed down somewhat to make it easier to explain what's wrong:
<StackPanel Orientation="Horizontal">
<TextBlock Name="DataGridTitle" />
<StackPanel
Margin="5,0"
Orientation="Horizontal"
>
<TextBlock />
<TextBox
Name="VerticallyExpandMe"
Margin="10,2,10,-1"
AcceptsReturn="True"
VerticalAlignment="Center"
Text="{Binding QueryString}"
>
</TextBox>
</StackPanel>
</StackPanel>
So I've removed your containing DockPanel and the two nested Border elements inside of that, because they're neither part of the problem nor relevant to the solution. So I'm starting at the pair of nested StackPanel elements in your example. And I've also removed most of the attributes because most of them are also not relevant to the layout.
This looks a bit weird - having two nested horizontal stack panels like this looks redundant, but it does actually make sense in your original if you need to make the nested one visible or invisible at runtime. But it makes it easier to see the problem.
(The empty TextBlock tag is also weird, but that's exactly as it appears in your original. That doesn't appear to be doing anything useful.)
And here's the problem: your TextBox is inside some horizontal StackPanel elements, meaning its width is unconstrained - you have inadvertently told the text box that it is free to grow to any width, regardless of how much space is actually available.
A StackPanel will always perform layout that is unconstrained in the direction of stacking. So when it comes to lay out that TextBox, it'll pass in a horizontal size of double.PositiveInfinity to the TextBox. So the TextBox will always think it has more space than it needs. Moreover, when a child of a StackPanel asks for more space than is actually available, the StackPanel lies, and pretends to give it that much space, but then crops it.
(This is the price you pay for the extreme simplicity of StackPanel - it's simple to the point of being bone-headed, because it will happily construct layouts that don't actually fit. You should only use StackPanel if either you really do have unlimited space because you're inside a ScrollViewer, or you are certain that you have sufficiently few items that you're not going to run out of space, or if you don't care about items running off the end of the panel when they get too large and you don't want the layout system to try to do anything more clever than simply cropping the content.)
So turning on text wrapping won't help here, because the StackPanel will always pretend that there's more than enough space for the text.
You need a different layout structure. Stack panels are the wrong thing to use because they will not impose the layout constraint you need to get text wrapping to kick in.
Here's a simple example that does roughly what you want:
<Grid VerticalAlignment="Top">
<DockPanel>
<TextBlock
x:Name="DataGridTitle"
VerticalAlignment="Top"
DockPanel.Dock="Left"
/>
<TextBox
Name="VerticallyExpandMe"
AcceptsReturn="True"
TextWrapping="Wrap"
Text="{Binding QueryString}"
>
</TextBox>
</DockPanel>
</Grid>
If you create a brand new WPF application and paste that in as the content of the main window, you should find it does what you want - the TextBox starts out one line tall, fills the available width, and if you type text in, it'll grow one line at a time as you add more text.
Of course, layout behaviour is always sensitive to context, so it may not be enough to just throw that into the middle of your existing application. That will work if pasted into a fixed-size space (e.g. as the body of a window), but will not work correctly if you paste it into a context where width is unconstrained. (E.g., inside a ScrollViewer, or inside a horizontal StackPanel.)
So if this doesn't work for you, it'll be because of other things wrong elsewhere in your layout - possibly yet more StackPanel elements elsewhere. From the look of your example, it's probably worth spending some time thinking about what you really need in your layout and simplifying it - the presence of negative margins, and elements that don't appear to do anything like that empty TextBlock are usually indicative of an over-complicated layout. And unnecessary complexity in a layout makes it much hard to achieve the effects you're looking for.
Alternatively, you could constrain your TextBlock's Width by binding it to a parent's ActualWidth, for example:
<TextBlock Width="{Binding ElementName=*ParentElement*, Path=ActualWidth}"
Height="Auto" />
This will force it to resize its height automatically too.
Use MaxWidth and TextWrapping="WrapWithOverflow".
I'm using another simple approach that allows me not to change the document layout.
The main idea is not to set the control Width before it starts changing. For TextBoxes, I handle the SizeChanged event:
<TextBox TextWrapping="Wrap" SizeChanged="TextBox_SizeChanged" />
private void TextBox_SizeChanged(object sender, SizeChangedEventArgs e)
{
FrameworkElement box = (FrameworkElement)sender;
if (e.PreviousSize.Width == 0 || box.Width < e.PreviousSize.Width)
return;
box.Width = e.PreviousSize.Width;
}
You can use this class which extends TextBlock. It does auto-shrinking and takes MaxHeight / MaxWidth into consideration:
public class TextBlockAutoShrink : TextBlock
{
private double _defaultMargin = 6;
private Typeface _typeface;
static TextBlockAutoShrink()
{
TextBlock.TextProperty.OverrideMetadata(typeof(TextBlockAutoShrink), new FrameworkPropertyMetadata(new PropertyChangedCallback(TextPropertyChanged)));
}
public TextBlockAutoShrink() : base()
{
_typeface = new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, this.FontStretch, this.FontFamily);
base.DataContextChanged += new DependencyPropertyChangedEventHandler(TextBlockAutoShrink_DataContextChanged);
}
private static void TextPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var t = sender as TextBlockAutoShrink;
if (t != null)
{
t.FitSize();
}
}
void TextBlockAutoShrink_DataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
FitSize();
}
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
FitSize();
base.OnRenderSizeChanged(sizeInfo);
}
private void FitSize()
{
FrameworkElement parent = this.Parent as FrameworkElement;
if (parent != null)
{
var targetWidthSize = this.FontSize;
var targetHeightSize = this.FontSize;
var maxWidth = double.IsInfinity(this.MaxWidth) ? parent.ActualWidth : this.MaxWidth;
var maxHeight = double.IsInfinity(this.MaxHeight) ? parent.ActualHeight : this.MaxHeight;
if (this.ActualWidth > maxWidth)
{
targetWidthSize = (double)(this.FontSize * (maxWidth / (this.ActualWidth + _defaultMargin)));
}
if (this.ActualHeight > maxHeight)
{
var ratio = maxHeight / (this.ActualHeight);
// Normalize due to Height miscalculation. We do it step by step repeatedly until the requested height is reached. Once the fontsize is changed, this event is re-raised
// And the ActualHeight is lowered a bit more until it doesnt enter the enclosing If block.
ratio = (1 - ratio > 0.04) ? Math.Sqrt(ratio) : ratio;
targetHeightSize = (double)(this.FontSize * ratio);
}
this.FontSize = Math.Min(targetWidthSize, targetHeightSize);
}
}
}
I met a problem when deveoping a photo viewer application.
I use ListBox to Show Images, which is contained in a ObservableCollection.
I bind the ListBox's ItemsSource to the ObservableCollection.
<DataTemplate DataType="{x:Type modeldata:ImageInfo}">
<Image
Margin="6"
Source="{Binding Thumbnail}"
Width="{Binding ZoomBarWidth.Width, Source={StaticResource zoombarmanager}}"
Height="{Binding ZoomBarWidth.Width, Source={StaticResource zoombarmanager}}"/>
</DataTemplate>
<Grid DataContext="{StaticResource imageinfolder}">
<ScrollViewer
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Disabled">
<ListBox Name="PhotosListBox"
IsSynchronizedWithCurrentItem="True"
Style="{StaticResource PhotoListBoxStyle}"
Margin="5"
SelectionMode="Extended"
ItemsSource="{Binding}"
/>
</ScrollViewer>
I also bind the Image'height in ListBox with a slider.(the slider's Value also bind to zoombarmanager.ZoomBarWidth.Width).
But I found if the collection become larger, such as: contains more then 1000 images, If I use the slider to change the size of iamges, it become a bit slow.
My Question is.
1. Why it become Slow? become it tries to zoom every images,or it just because notify("Width") is invoked more than 1000 times.
2. Is there any method to solve this kind of problem and make it faster.
The PhotoListBoxStyle is like this:
<Style~~ TargetType="{x:Type ListBox}" x:Key="PhotoListBoxStyle">
<Setter Property="Foreground" Value="White" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}" >
<WrapPanel
Margin="5"
IsItemsHost="True"
Orientation="Horizontal"
VerticalAlignment="Top"
HorizontalAlignment="Stretch" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style~~>
But If I use the Style above, I have to use ScrollViewer outside ListBox, otherwise I have no idea how to get a smooth scrolling scrollerbar and the wrappanel seems have no default scrollerbar. Anyone help? It is said listbox with scrollviewer has poor performance.
The problem is that your new Layout Panel is the WrapPanel and it doesn't support Virtualization! It is possible to create your own Virtualized WrapPanel... Read more here
Also read more about other issues like the implementation IScrollInfo here
I also highly recommend that your do not create a new control template just to replace the layout panel... Rather do the following:
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
The advantage of doing this is that you do not need to wrap your listbox in a scrollviewer!
[UPDATE] Also read this article by Josh Smith! To make the WrapPanel wrap... you also have to remember to disable horizontal scrolling...
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
I am not familiar with this component, but in general there is going to be limitations on the number of items a listbox can display at one time.
A method to solve this kind of problem is to keep the number of images loaded in the control within the number the control can display at acceptable performance levels. Two techniques to do this are paging or dynamic loading.
In paging, you add controls to switch between discrete blocks of pictures, for example, 100 at a time, with forward and back arrows, similar to navigating database records.
With dynamic loading, you implement paging behind the scenes in such a way that when the user scrolls to the end, the application automatically loads in the next batch of pictures, and potentially even removes a batch of old ones to keep the responsiveness reasonable. There may be a small pause as this occurs and there may be some work involved to keep the control at the proper scroll point, but this may be an acceptable trade-off.
I would recommend you not bind the Width/Height property of each individual image, but rather you bind a LayoutTransform on the ListBox's ItemsPanel. Something like:
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel>
<StackPanel.LayoutTransform>
<ScaleTransform
ScaleX="{Binding Path=Value, ElementName=ZoomSlider}"
ScaleY="{Binding Path=Value, ElementName=ZoomSlider}" />
</StackPanel.LayoutTransform>
</StackPanel>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
Part of the problem is that it is loading the full image in each. You have to use an IValueConverter to open each image in a thumbnail size by setting either the DecodePixelWidth or DecodePixelHeight properties on the BitmapImage. Here's an example I use in one of my projects...
class PathToThumbnailConverter : IValueConverter {
public int DecodeWidth {
get;
set;
}
public PathToThumbnailConverter() {
DecodeWidth = 200;
}
public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) {
var path = value as string;
if ( !string.IsNullOrEmpty( path ) ) {
FileInfo info = new FileInfo( path );
if ( info.Exists && info.Length > 0 ) {
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.DecodePixelWidth = DecodeWidth;
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.UriSource = new Uri( info.FullName );
bi.EndInit();
return bi;
}
}
return null;
}
public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) {
throw new NotImplementedException();
}
}
try to virtualize your stackpael with the VirtualizingStackPanel.IsVirtualizing="True" attached property. this should increase performance.
using a listbox with many items in a scrollviewer is another known performance issue within wpf. if you can, try to get rid of the scrollviewer.
if your itemtemplates are kinda complex you should consider using the Recycling VirtualizationMode. this tells your listbox to reuse existing objects and not create new ones all the time.
What does your PhotoListBoxStyle style look like? If it's changing the ListBox's ItemsPanelTemplate then there's a good chance your ListBox isn't using a VirtualizingStackPanel as its underlying list panel. Non-virtualized ListBoxes are a lot slower with many items.