I am designing something similar a PropertyGrid where I want to show properties of objects. For special reasons I am not going to use the PropertyGrid but create my own.
For each property I have created a custom usercontrol. Now to my horror the performance is very bad. If I have something like 100 properties it takes 500 milliseconds to show them in a StackPanel/Listbox.
I did an experiment where I add 200 default UserControls to a StackPanel. It took about 50 milliseconds. Still a very high number I think.
Should I not use usercontrols for such a purpose? It seems very object-oriented to do it this way and I can not really see another solution.
However I can see that PropertyGrid and TreeView performs good, so what have they done and what should I do?
Edit:
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
using (var suspend = Dispatcher.DisableProcessing())
{
// Add all children here
for (int i = 0; i < 200; i++)
{
this.propertiesStackPanel.Children.Add(new System.Windows.Controls.Button(){Content = "Testing"});
}
}
stopwatch.Stop();
This still takes about 50 milliseconds. If I change to my own custom usercontrol it is much higher. I might add that scrolling is not a problem.
Edit2:
OK. It has nothing to do with stackpanel. I have found out that it is because creating UserControls is a very expensive operation. If you have any other idea of what to do I would gladly hear them :)
Edit3:
Nothing is going on in the constructor of my usercontrol other than InitializeComponent method. Here is an example of a usercontrol I am adding.
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="PropertyBox.GroupUC"
x:Name="UserControl"
d:DesignWidth="640" d:DesignHeight="480" Background="#FF32B595" BorderThickness="0">
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20px"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Border x:Name="border" BorderThickness="0,1" Grid.Column="1">
<TextBox Text="TextBox" TextWrapping="Wrap" VerticalAlignment="Center" HorizontalAlignment="Right" BorderThickness="0" Padding="0" Visibility="Hidden"/>
</Border>
<Label x:Name="groupNameLabel" HorizontalAlignment="Left" Margin="5,0,0,0" VerticalAlignment="Center" Content="Label" Padding="0" Grid.Column="1"/>
<Button x:Name="expandButton" HorizontalAlignment="Left" VerticalAlignment="Center" Width="12" Height="12" Content="" Click="ExpandButtonClick" Margin="4,0,0,0" Padding="0" Grid.ColumnSpan="2" d:IsHidden="True"/>
<Image x:Name="expandButton2" Visibility="Hidden" Width="12" Height="12" HorizontalAlignment="Center" VerticalAlignment="Center" Stretch="None"/>
</Grid>
My suspicion is that you're triggering many layout updates while adding your hundreds of children.
If that is the bottleneck, you may want to consider doing:
using(var suspend = Dispatcher.DisableProcessing())
{
// Add all children here
}
This will cause the dispatcher to stop processing messages while you add your controls, and do the entire layout and render in one pass at the end.
Related
I have a WPF application and I'm trying to get the elements positioned correctly. There are just four elements, so it should be pretty straight-forward, but I just can't get it working.
One wrinkle is that the window resizes itself to (about) the size of the desktop window when it appears, so it doesn't have a fixed size.
The elements are supposed to be stacked from top to bottom, so a Stack Panel seemed natural. But The third element has to take up all the remaining space that the top two and bottom ones don't. No matter what I tried, it either took up too much space, or too little. I could only seem to get it working if I gave it a concrete pixel size which, as explained above, won't work.
The latest thing I've tried is a Dock Panel. While it looks correct in the Visual Studio designer, when executed, the third element--a Canvas--completely covers the bottom element.
My XAML:
<DockPanel>
<Button x:Name="btnClose" DockPanel.Dock="Top" Content="X"
HorizontalAlignment="Right" Margin="0,5,5,0" VerticalAlignment="Top"
Width="Auto" Height="Auto" Background="Black"
Foreground="White" Click="btnClose_Click"/>
<Label x:Name="lblTitle" DockPanel.Dock="Top" Content="My Title"
HorizontalAlignment="Center" VerticalAlignment="Top" Width="Auto"
Foreground="White" FontWeight="Bold" FontSize="22"/>
<Label x:Name="lblControls" DockPanel.Dock="Bottom" Content="Placeholder"
HorizontalAlignment="Center" VerticalAlignment="Bottom" Width="Auto"
Height="Auto" Foreground="White" FontWeight="Bold" FontSize="22"/>
<Border x:Name="CanvasBorder" BorderBrush="White" BorderThickness="5" >
<Canvas x:Name="cvsChart" Grid.Row="0" HorizontalAlignment="Stretch"
VerticalAlignment="Top" Width="Auto">
</Canvas>
</Border>
</DockPanel>
Any idea about how to get that Canvas to stretch and fill all the space the other three don't take?
UPDATE
Since #Peter Duniho pretty much proved to me that the code worked, I tried an experiment and removed the resizing code I have in place for when the window appears. Taking it out, the window appears absolutely correctly. This is what I do to resize it to (mostly) the desktop size:
public const int WINDOW_OFFSET = 10;
...
int screenWidth = (int)System.Windows.SystemParameters.PrimaryScreenWidth;
int screenHeight = (int)System.Windows.SystemParameters.PrimaryScreenHeight;
// center this window in desktop
Width = screenWidth - WINDOW_OFFSET;
Height = screenHeight - WINDOW_OFFSET;
Left = WINDOW_OFFSET/2;
Top = WINDOW_OFFSET/2;
So I did some poking around, and found a comment here on the 'Stack that said to get the WorkArea instead of the PrimaryScreenHeight. I tried that and voila!, the whole application window appears.
int screenWidth = (int)System.Windows.SystemParameters.WorkArea.Width;
int screenHeight = (int)System.Windows.SystemParameters.WorkArea.Height;
As it turns out, the bottom row was displaying, I just couldn't see it because it appeared below the bottom of the screen. Now I can see it, and I'm back to development heaven!
Thanks to everyone for their input!
There are a number of possible approaches to this. One of the most straightforward is to contain your elements in a Grid and set all but the third row height to Auto:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Button x:Name="btnClose" Content="X" Grid.Row="0"
HorizontalAlignment="Right" Margin="0,5,5,0" VerticalAlignment="Top"
Width="Auto" Height="Auto" Background="Black"
Foreground="White" Click="btnClose_Click"/>
<Label x:Name="lblTitle" Content="My Title" Grid.Row="1"
HorizontalAlignment="Center" VerticalAlignment="Top" Width="Auto"
Foreground="White" FontWeight="Bold" FontSize="22"/>
<Label x:Name="lblControls" Content="Placeholder" Grid.Row="3"
HorizontalAlignment="Center" VerticalAlignment="Bottom" Width="Auto"
Height="Auto" Foreground="White" FontWeight="Bold" FontSize="22"/>
<Border x:Name="CanvasBorder" BorderBrush="White" BorderThickness="5" Grid.Row="2">
<Canvas x:Name="cvsChart" Grid.Row="0" HorizontalAlignment="Stretch"
VerticalAlignment="Top" Width="Auto">
</Canvas>
</Border>
</Grid>
The default setting for a grid's row definition height is "*", which says to distribute all of the remaining space among all the rows with that setting. With only one row using that setting, it gets all of the leftover space.
This produces a window that looks like this:
(I set the window background to Gray so that your white text and border would be visible.)
Another option would in fact be to use DockPanel. It appears to me that the main problem in your attempt is that you set the lblControls element to DockPanel.Dock="Bottom" when it should be Top instead. When I change it to Top, it seems to work fine for me.
Based on your comment below, it seems you actually did want lblControls to be set to DockPanel.Dock="Bottom", and in fact the code you posted seems to also do what you want. It's not clear to me what is different from what the code you posted does and what you want it to do. It would be better if you would provide a good Minimal, Complete, and Verifiable code example that reliably reproduces the problem.
Remove the vertical alignment of the Canvas
I want to create a custom user control with two grids in which I want to load images and until images are loaded I want to show the progressRing control. The problem occurs when I add a second ProgressRing. My XAML looks like this:
<Grid Margin="0,0,0,21" Background="{ThemeResource PhoneAccentBrush}">
<Grid x:Name="leftImage" Margin="10" Width="190" Height="190"
HorizontalAlignment="Left">
<Image x:Name="imageHolderLeft" x:FieldModifier="public" Width="180"
Height="180" ImageFailed="imageHolderLeft_ImageFailed"
ImageOpened="imageHolderLeft_ImageOpened"/>
<Grid>
<ProgressRing x:Name="waitImageLeft" IsActive="True"
VerticalAlignment="Center" HorizontalAlignment="Center"
Background="Transparent"
Foreground="{ThemeResource AppBarBackgroundThemeBrush}"/>
</Grid>
</Grid>
<Grid x:Name="rightImage" Margin="10" Width="190" Height="190"
HorizontalAlignment="Right">
<Image x:Name="imageHolderRight" x:FieldModifier="public" Width="180"
Height="180" ImageOpened="imageHolderRight_ImageOpened"
ImageFailed="imageHolderRight_ImageFailed"/>
<Grid>
<ProgressRing x:Name="waitImageRight" IsActive="True"
VerticalAlignment="Center" HorizontalAlignment="Center"
Background="Transparent"
Foreground="{ThemeResource AppBarBackgroundThemeBrush}"/>
</Grid>
</Grid>
</Grid>
So when I comment out one ProgressRing it works fine, but when there are two of them my program crashes with the following error: Layout cycle detected. Layout could not complete
Does anyone knows why?
Thanks :)
This error indicates that the layout of an element depends on other elements that indirectly depend on the original element. Windows was not able to figure out the overall layout... Much like an infinite loop or infinite recursion.
In your case the cause probably relates to the alignments and sizes. You should be able to solve the problem by simplifying the layout. Keep the outer Grid but add 5 ColumnDefinitions, the middle one having width * and the other ones width Auto. Get rid of the other 4 Grids. Instead, put the two images and progress rings directly into the main Grid in columns number 0, 1, 3, and 4 (using the Grid.Column attached property). Put the desired sizes on the Width and Height properties of the images and progress rings, not on the Grid.
NOTE: This is one of the first time I'm using WPF.
I am trying to align a certain control, let's say a button for now, in the bottom right corner. But when I debug my application, it misses 8 pixels to the bottom and right. I will attach 2 pictures to show you what happens.
How do I keep the button in place?
My XAML code:
<Window x:Class="Plugin_Manager.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Plugin Manager" Height="350" Width="525" Loaded="Window_Loaded_1">
<Grid x:Name="GridMain">
<Button Content="Refresh" Margin="432,288,0,0" VerticalAlignment="Top" HorizontalAlignment="Left" Width="75"/>
<ListView HorizontalAlignment="Left" Height="273" Margin="10,10,0,0" VerticalAlignment="Top" Width="497">
<ListView.View>
<GridView>
<GridViewColumn/>
</GridView>
</ListView.View>
</ListView>
</Grid>
If you choose to use Grid layout you should try to avoid placing objects via Margin. Margin should be used to create buffer around an object, not move it to a specific point in the window. Use the layout manager's power to your advantage!
Here is a Grid example that does what you are looking for.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListView Grid.Row="0" />
<Button Grid.Row="1" HorizontalAlignment="Right" Content="Push Me" />
</Grid>
I would also read up on Layout Manager in WPF. There are several; each having its own advantages & disadvantages.
Here is a DockPanel version.
<DockPanel>
<Button DockPanel.Dock="Bottom" HorizontalAlignment="Right" Content="Push Me" />
<ListView />
</DockPanel>
To create your buffer between the button and the window chrome you could do a few different things:
<Grid Margin="10"> will apply a 10 pixel space between all content and the window chrome on all side.
<Grid Margin="0,0,10,10"> would indent all content, but only on the right & bottom.
<Grid Margin="10,0,10,10"> indents all around, except the top (I commonly do this one, with a different margin value).
<Button Margin="0,0,10,10"> would indent only the button from the chrome (this is the direct answer to your comment question).
Replace the Grid above with DockPanel for the second example, or whatever other Layout Manager you are using.
A usability side note: Your confirmation buttons (I'm assuming your button will be an Ok/Cancel type button) should not be indented differently from the rest of your content. All controls that butt up against the right margin should do so at the same point (i.e., you can draw a vertical line down the right side of them all).
So, using your question's example: your button should not be indented 10 pixels to the right while your list box is not. Keeping things lined up will improve the overall look to your application.
(this ends my "usability and look-and-feel is important" side note) :)
<Button VerticalAlignment="Bottom" HorizontalAlignment="Right" Margin="5"/>
Some code example will help. Try using the alignment in xaml for your button as shown below. Ensure that the margins on the button are 0.
<Button Margin="0" HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
Looking at the sample code, it is your margins and the alignment you have that are probably causing that.
Just some pointers that may help. Instead of using large margins to align the controls, I find it much easier to work with Column and Row definitions on the grid. This way you can align your controls using the grid and they will size properly as you resize your window. I attached an example in hopes it helps in your new adventures with WPF!
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="150"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Grid.Row="0" Text="Version Date" Margin="3" VerticalAlignment="Center"/>
<TextBlock Grid.Column="0" Grid.Row="1" Text="{Binding DateSubmitted}" Margin="3"/>
<TextBlock Grid.Column="1" Grid.Row="0" Text="Report" Margin="3" VerticalAlignment="Center"/>
<TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding ReportName}" Margin="3"/>
</Grid>
I have a simple wpf application. In main window i have stack panel and 2 buttons. First button adds 100 my user controls (without any data bindings, events, bitmaps), and second removes all of them from panel and calls GC.Collect(). And there are some problems:
1. After i clicked "remove" button first time not all my memory releases, and I must click it few times to release more memory.
2. After 5 - 10 min memory releases but few megabytes dont.
for example after my app starts it takes ~22mb
when i adding 500 controls - ~60mb
after i clicked "remove" button first time - ~55mb (I wait some time, memory not deallocated)
i click few times and memory fell down to 25mb,
I dont understand this, I am new in WPF, and maybe i miss something
I want to release memory immediately.
<Window x:Class="WpfApplication10.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="385" Width="553">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="25" />
<RowDefinition Height="240*" />
<RowDefinition Height="25" />
</Grid.RowDefinitions>
<Grid
Name="border1"
Grid.Row="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" >
<ScrollViewer VerticalAlignment="Stretch"
Name="scrollViewer1"
HorizontalAlignment="Stretch">
<StackPanel
Margin="3,3,3,3"
Background="Transparent"
VerticalAlignment="Stretch"
Name="activityStackPanel"
HorizontalAlignment="Stretch">
</StackPanel>
</ScrollViewer>
</Grid>
<Button Content="Button" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="12,0,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
<Button Content="Button" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="141,0,0,0" Name="button2" VerticalAlignment="Top" Width="75" Click="button2_Click" />
<Label Content="Label" Grid.RowSpan="2" Height="28" HorizontalAlignment="Left" Margin="34,0,0,0" Name="label1" VerticalAlignment="Top" />
</Grid>
namespace WpfApplication10
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
int N = 100;
//var r = new ActivityStatisticItem("111", "222", DateTime.Now, "333", 1);
for (int i = 0; i < N; i++)
{
activityStackPanel.Children.Add(new UserControl1());
}
label1.Content = activityStackPanel.Children.Count;
}
private void button2_Click(object sender, RoutedEventArgs e)
{
activityStackPanel.Children.Clear();
label1.Content = activityStackPanel.Children.Count;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}
}
<UserControl x:Class="WpfApplication10.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="Transparent"
Margin="0,2,0,2"
MinHeight="80"
MinWidth="130"
MaxHeight="80">
<Grid Width="441">
<Grid.RowDefinitions>
<RowDefinition Height="40" Name="rowTop" />
<RowDefinition Height="40" Name="rowBottom"/>
</Grid.RowDefinitions>
<Border BorderBrush="Gray"
BorderThickness="1"
HorizontalAlignment="Stretch"
Background="LightGreen"
Name="contactPanel"
CornerRadius="3,3,3,3"
VerticalAlignment="Stretch" Panel.ZIndex="1" >
<Grid
VerticalAlignment="Stretch"
Name="grid1"
Margin="3,0,3,0"
HorizontalAlignment="Stretch">
<Label Content="Contact" Height="15" HorizontalAlignment="Left" Margin="15,3,0,0" Name="headerLabel" Padding="0" VerticalAlignment="Top" FontSize="10" FontWeight="DemiBold"/>
<Label Content="00/00/0000 00:00:00" Height="15" HorizontalAlignment="Left" Margin="13,18,0,0" Name="timeLabel" Padding="0" VerticalAlignment="Top" FontSize="10" Width="100" FontWeight="DemiBold" />
<Label Content="00:00:00" Height="15" HorizontalAlignment="Right" Margin="0,18,0,0" Name="durationLabel" Padding="0" VerticalAlignment="Top" FontSize="10" Width="38" FontWeight="DemiBold"/>
<!--<Image Height="12" HorizontalAlignment="Left" Margin="0,3,0,0" Name="directionPictureBox" Stretch="Fill" VerticalAlignment="Top" Width="12" />
<Image Height="12" HorizontalAlignment="Right" Margin="0,20,41,0" Name="timerImage" Stretch="Fill" VerticalAlignment="Top" Width="12" />
<Image Height="12" HorizontalAlignment="Left" Margin="0,20,0,0" Name="dateTimeImage" Stretch="Fill" VerticalAlignment="Top" Width="12" />-->
</Grid>
</Border>
<Border BorderBrush="Gray"
BorderThickness="1,0,1,1"
Grid.Row="1"
Background="White"
HorizontalAlignment="Stretch"
Margin="10,0,10,0"
Name="detailsPanel"
CornerRadius="0,0,3,3"
VerticalAlignment="Stretch">
<Grid HorizontalAlignment="Stretch"
Name="grid2"
Margin="3,0,3,0"
VerticalAlignment="Stretch">
<Label Content="Label" Height="15" HorizontalAlignment="Stretch" FontSize="9" Padding="0" Margin="0,3,0,0" Name="numberRadLabel" VerticalAlignment="Top" />
<Label Content="Label" Height="15" HorizontalAlignment="Stretch" FontSize="9" Padding="0" Margin="0,18,0,0" Name="queueRadLabel" VerticalAlignment="Top" />
</Grid>
</Border>
</Grid>
In user control i have only
public UserControl1()
{
InitializeComponent();
}
I want to release memory immediately.
Don't. Trust GC.
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Don't. Trust GC.
After 5 - 10 min memory releases
Didn't I say trust GC?
Garbage collection model will make sure the unwanted managed memory in your system is released (which includes almost all of your controls memory). It uses an algorithm for optimising which includes generations, free memory available, possibly CPU available... so GC.Collect() will interfere with it.
GC.Collect() is asynchronous so no immediate effect.
The only resource you need to be careful is the unmanaged resource which usually is handled by Dispose Pattern. Otherwise don't mess with GC, it does its job very well.
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
This is a surefire way of forcing non-GCable objects into Gen2 prematurely, thus increasing your memory footprint for a longer period of time, for no good reason.
As Aliostad said: don't!
Leave the garbage collector alone and let it do its job.
What you're describing isn't a memory leak. It's dynamic memory not getting released at the moment you think it ought to be released.
Are you the garbage collector? You are not. Worrying about when garbage gets collected isn't your job. If these objects are actually garbage - and they are - the memory will be there when you need it.
In a garbage collected environment, releasing memory immediately doesn't really make sense.
Since the CLR JITs code on demand, the first time you run your test you shouldn't see memory drop back to where it was initially. This makes sense because new code paths have been followed and code has been JITted. That code needs to reside somewhere in memory no?
Therefore, after your first test run, you shouldn't be able to collect back down to your initial memory footprint. Your baseline should be the memory usage you get after running the test once, not before. After running a second time, I am able to get memory back down to the baseline with a number of collections.
Also, I'd recommend running your project in release mode with no debugger attached. Running your program with a debugger attached will not show you a true memory profile, as there are various tricks it employs to keep objects around (e.g. Collect objects still in scope - GC.Collect).
This is all a moot point, however, because like I said above, reclaiming memory immediately doesn't make much sense in a GC environment (in most cases).
I would concur with #Aliostad re GC. I does its job very well, BUT it is not a tool to magically clear all of your memory.
If you have memory leak problems, the most straightforward and reliable solution is to use a profiler, whih should be able to identify if you have a genuine leak and where it is. I have used Ants from Red Gate, but others may have better suggestions.
As well as following the usual guidelines, like making sure you properly dispose of stuff. Calling GC and hoping that it will work is not an alternative for proper code assessment.
By using this Dll Invoke we can realocate the memory resources
public class MemoryManagement
{
[DllImportAttribute("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize", ExactSpelling = true, CharSet =
CharSet.Ansi, SetLastError = true)]
private static extern int SetProcessWorkingSetSize(IntPtr process, int minimumWorkingSetSize, int
maximumWorkingSetSize);
public static void FlushMemory()
{
GC.Collect();
GC.WaitForPendingFinalizers();
if (Environment.OSVersion.Platform == PlatformID.Win32NT) { SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
}
}
I have about 45 decently large images (about 680x1000) that need to be loaded into a simple user control (rounded backborder with fill, image, textblock, and 2 side rectangles) and then displayed in a wrappanel. Virtualizing won't really help here since the images are to be all visible at program load.
I know inside of the BitmapImage init i can set the decodepixel width, which does help a little, however id like to load them all as full size since i want to be able resize the images with a slider without losing quality (this part works fast for the most part). I know one possibility would be to set the decodewidth to be some number which i set as the max viewable size could help.
I tried the multithreaded approach found in How do I load images in the background? (first answer), however it caused the program to take a LOT longer to load!
Any ideas?
Current load code:
BitmapImage bmp = new BitmapImage();
bmp.BeginInit();
//bmp.DecodePixelWidth = 400;
bmp.UriSource = new Uri(file.FullName);
bmp.EndInit();
bmp.Freeze();
images.Add(bmp);
Sample XAML code:
<Border x:Name="backBorder" Background="Black" Padding="2" Margin="3" CornerRadius="3,3,4,4"
BorderBrush="Black" BorderThickness="1"
MouseEnter="backBorder_MouseEnter" MouseLeave="backBorder_MouseLeave" MouseLeftButtonUp="backBorder_MouseLeftButtonUp" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="16" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="15" />
</Grid.ColumnDefinitions>
<Image x:Name="imageBox" Stretch="Fill" Width="{Binding Path=ImageWidth, ElementName=me}" Height="{Binding Path=ImageHeight, ElementName=me}" />
<Border x:Name="backRatingBorder" Grid.Column="1" Margin="3,0,0,0" BorderBrush="Blue" Background="White" BorderThickness="1"/>
<Border x:Name="frontRatingBorder" Grid.Column="1" Margin="3,0,0,0" BorderBrush="Blue" Background="LightBlue" BorderThickness="1" VerticalAlignment="Bottom" Height="50"/>
<TextBlock x:Name="textBlock" Grid.Row="1" Grid.ColumnSpan="2" TextAlignment="Center" Background="Transparent" Foreground="White" FontFamily="Segoe UI" FontWeight="SemiBold" FontSize="12" />
</Grid>
</Border>
.
UPDATE:
Well i ended up making it more responsive by running the load image loop in a single background worker. After each image is loaded, Dispacher.Invoke is called to create the wrap item. After playing with it for a while i got it to show each item as it is created in the same time it took before.
If you're happy with the overall performance, just the loading of the images, you could try this Multithreaded UI tutorial. I managed to get it to work quite easily, but if you're loading all the images in a loop it won't update the visual until you've finished loading all of the images. The UI is responsive during this time, however, as all the loading is on a separate thread.
Alternativly, if you're loading all your images in a loop then you could try an improved version of Windows Forms DoEvents method (scroll down to the example). You'd call this after loading each image and it will give the UI a chance to update itself (process user interaction etc). This is the approach I used when loading map tiles for my project and is easier than the first.