WPF memory leak - c#

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

Related

Making option to choose correct resolution

I am making a video game and have problem with screen resolutions.
Earlier, a few month ago, I asked very similar question.
Need images with lower resolution "stretched" to screen size
Back then, it seemed the answer I got (using ViewBox) was perfect, but now I am getting problems.
I want every and every image and control in my game to have a few different possible variations fit for resolutions.
For example, if user sets resolution 800x600, all images and buttons are reduced to correct sizes. But I do want my game to be fullscreen only, not windowed.
So, if the resolution is lower then end-user monitor has, all images must be stretched and look "fuzzy". And if higher, part of it must be outside of screen.
For now, my code just sets resolution to end-user monitor, whatever it is. That's absolutely not what I want.
What I get:
A very crude example of what I need:
I'll show xaml as its now. Of course it isn't full, but I'll show the beginning and a few elements, so you'll know how it's built.
<Window
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:Name="wdwMain" x:Class="RealityIncognita.MainWindow"
Height="900" Width="1600" ResizeMode="NoResize" WindowState="Maximized" Cursor="Cross" WindowStyle="None" Loaded="wdwMain_Loaded">
<Viewbox x:Name="viewMain" Stretch="Fill">
<Grid x:Name="areaContainer" HorizontalAlignment="Left" Height="900" VerticalAlignment="Top" Width="1600">
<Grid x:Name="areaMain">
<Grid.Background>
<ImageBrush ImageSource="Resources/Images/Interface/main_interface.jpg"/>
</Grid.Background>
<Label x:Name="lblTextOutput" Content="Label" HorizontalAlignment="Center" Height="52" Margin="55,726,31,0" VerticalAlignment="Top" Width="1514" FontFamily="Arial" FontSize="22" FontWeight="Bold" HorizontalContentAlignment="Center"/>
<Button x:Name="btnExit" HorizontalAlignment="Left" Height="106" Margin="1464,771,0,0" VerticalAlignment="Top" Width="126" Click="btnExit_Click" Background="{x:Null}" BorderBrush="{x:Null}" Foreground="{x:Null}">
<Button.Template>
<ControlTemplate>
<Image Source="/Resources/Images/Interface/Blank.png" Stretch="Fill" Margin="12,0,6,0"/>
</ControlTemplate>
</Button.Template>
</Button>
<Grid x:Name="areaShowers" HorizontalAlignment="Left" Height="700" Margin="1653,790,-1561,-590" VerticalAlignment="Top" Width="1508" IsVisibleChanged="areaShowers_IsVisibleChanged">
<Grid.Background>
<ImageBrush ImageSource="Resources/Images/Rooms/Showers/shower_room.jpg" />
</Grid.Background>
<Button x:Name="objShowersSoap" HorizontalAlignment="Left" Height="29" Margin="613,423,0,0" VerticalAlignment="Top" Width="17" MouseLeave="MouseLeaveAnyObject" RenderTransformOrigin="11.706,1.897" Click="objShowersSoap_Click" MouseEnter="objShowersSoap_MouseEnter">
<Button.Template>
<ControlTemplate>
<Image Source="Resources/Images/Rooms/Showers/soap.png" Stretch="Fill" Margin="0,0,0,0"/>
</ControlTemplate>
</Button.Template>
</Button>
<Image x:Name="imgShowersOpenMachine" HorizontalAlignment="Left" Margin="0,0,0,0" VerticalAlignment="Top" Source="Resources/Images/Rooms/Showers/drying_machine_open.png" RenderTransformOrigin="0.808,0.471" Stretch="Fill"/>
</Button>
</Grid>
<Grid x:Name="areaLockerRoom" Height="700" VerticalAlignment="Top" Width="1508" IsVisibleChanged="areaLockerRoom_IsVisibleChanged" Margin="1653,17,-1561,0" MouseDown="areaLockerRoom_MouseDown" MouseEnter="areaLockerRoom_MouseEnter" MouseMove="areaLockerRoom_MouseMove">
<Grid.Background>
<ImageBrush ImageSource="Resources/Images/Rooms/LockerRoom/locker_room_ready.png"/>
</Grid.Background>
<Button x:Name="objLockerRoomCrowbar" Content="" HorizontalAlignment="Left" Height="243" Margin="604,328,0,0" VerticalAlignment="Top" Width="51" MouseEnter="objCrowbar_MouseEnter" Panel.ZIndex="1" Click="objCrowbar_Click" MouseLeave="MouseLeaveAnyObject">
<Button.Template>
<ControlTemplate>
<Image Source="Resources/Images/Rooms/LockerRoom/crowbar_only.png" Stretch="Fill" Margin="0,0,0,0"/>
</ControlTemplate>
</Button.Template>
</Button>
<Button x:Name="objLockerRoomOdyssey" HorizontalAlignment="Left" Height="53" Margin="797,638,0,0" VerticalAlignment="Top" Width="61" Click="objBookOdyssey_Click" MouseLeave="MouseLeaveAnyObject" MouseEnter="objBookOdyssey_MouseEnter">
<Button.Template>
<ControlTemplate>
<Image Source="Resources/Images/Rooms/LockerRoom/img_book_odyssey.png" Stretch="Fill" Margin="0,0,0,0"/>
</ControlTemplate>
</Button.Template>
</Button>
<Button x:Name="objLockerRoomEdda" HorizontalAlignment="Left" Height="53" Margin="1335,549,0,0" VerticalAlignment="Top" Width="61" MouseLeave="MouseLeaveAnyObject" Click="objBookEdda_Click" MouseEnter="objBookEdda_MouseEnter">
<Button.Template>
<ControlTemplate>
<Image Source="Resources/Images/Rooms/LockerRoom/img_book_edda.png" Stretch="Fill" Margin="0,0,0,0"/>
</ControlTemplate>
</Button.Template>
</Button>
To summarize the structure is like that:
Main Window - Viewbox - areaContainer (main grid) - game areas (grids) - images and buttons for each grid
To summarize:
What I get: game screens are set for end-user monitor resolution.
What I need: game sets all images to specific resolution, makes it "fuzzy" if resolution is LOWER then end-user's, and "cuts" it if it is HIGHER.
Thank you in advance,
Evgenie
ADDED:
If I understand correctly how it works - the container grid that includes all other items should be resized, images should become smaller (by default they are 1600x900), if needed. Then, this container grid must fit user's screen resolution, staying with small images quality.
And to simplify it even more: Can I make large images smaller, then change them to be big again (and lose quality) directly in Visual Studio?
Actually simply adjusting the viewbox did the trick.
<Viewbox x:Name="viewMain" VerticalAlignment="Center" HorizontalAlignment="Center">
And in code, I had to change MaxWidth and MaxHeight, instead of regular ones.
viewMain.MaxWidth = 1024;
viewMain.MaxHeight = 768;

XAML element fill all remaining space

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

WPF - UserControls very slow

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.

c# settings + StackOverflowException

I have a usercontrol in my wpf application which is causing a stackoverflowexception to be caught when it is instanced more than once. I tried to debug the cause of the exception and it is raised during InitializeComponent of my Usercontrol. When I enter InitializeComponent it jumps over to the app.xaml.cs codebehind and reads values which are contained in the Settings class.
I am "new" to using C# application settings so I have not experienced this error before. Not sure if this is commonplace or not when working with them. Also, this is the only usercontrol currently in my app that allows modification of the settings variables and it is the only usercontrol which exhibits this behavior.
I think my problem has something to do with the DebugOptions class using a datacontext of "Application.Current" and then I create another instance with that same datacontext but as soon as I access any of its properties I get the application confused about which obj is which. While that makes sense in my head, logically it doesn't work out that way because this usercontrol is instanced upon a button click and it's host panel is cleared before adding to prevent multiple instances from rolling around.
Posted below is the xaml and codebehind of my usercontrol. It has no dependencies except for the CLR properties from the App class that it binds to. I wish I had more info to provide on this but it's a very odd exception that creeps up.
Here is the property in my App class which causes the stackoverflow exception when it is "Get" accessed.
private Byte _debuglevel = Merlin.Properties.Settings.Default.DebugLevel;
public Byte DebugLevel
{
get { return _debuglevel; }
set { _debuglevel = value; }
}
public partial class DebugOptions : UserControl
{
public DebugOptions()
{
InitializeComponent();
}
private void ChangeLogDirectoryButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
MessageBox.Show("Make a decision here...choose to use the old winforms folder browser control or find one on the web because the std openfiledialog can't be overriden to select folders only.", "Fix this..");
}
private void UpdateDebugOptionsButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
//update debug level
Merlin.Properties.Settings.Default.DebugLevel = (Byte)DebugLevelSlider.Value;
//update log boolean
if ((bool)EnableLoggingRadioButton.IsChecked)
{
Merlin.Properties.Settings.Default.LogsEnabled = true;
}
else
{
Merlin.Properties.Settings.Default.LogsEnabled = false;
}
//update log path?
//save "settings"
Merlin.Properties.Settings.Default.Save();
//write a log event noting changes
App myappreference = (App)Application.Current;
Merlin.Helper.logger.pLogToFile(string.Format("Log Settings Updated at {0} by {1}", DateTime.Now.ToString(), myappreference.CurrentUser.UserName));
}
private void OpenLogDirectoryButton_Click(object sender, System.Windows.RoutedEventArgs e)
{
Process process = new Process();
Process.Start("Explorer.exe", Merlin.Properties.Settings.Default.LogsDirectory);
}
}
Usercontrol resources and UserControl tags omitted for brevity
<Border BorderBrush="Black" BorderThickness="1" Margin="0">
<Grid DataContext="{Binding Source={x:Static Application.Current}}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Content="Debug Options" HorizontalAlignment="Center" Margin="0" Grid.Row="0" VerticalAlignment="Center" FontSize="29.333" Style="{StaticResource UserControlTitleLabelStyle}" />
<StackPanel Orientation="Horizontal" Grid.Row="1">
<Label Content="Set Debug Level" HorizontalAlignment="Left" VerticalAlignment="Center"/>
<Slider x:Name="DebugLevelSlider" HorizontalAlignment="Left" VerticalAlignment="Center" Maximum="10" Value="{Binding DebugLevel}" Minimum="1" Margin="62,0,0,0" TickPlacement="BottomRight" SmallChange="1" Style="{StaticResource SliderStyle1}" Width="119">
<Slider.ToolTip>
<ToolTip Content="{Binding DebugLevel}" ContentStringFormat="{}The current value is {0} out of 10"/>
</Slider.ToolTip>
</Slider>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="2">
<Label Content="Application Level Logging: " />
<RadioButton x:Name="EnableLoggingRadioButton" GroupName="Logs" Content="Enable" Margin="5" IsChecked="{Binding LogsEnabled}">
<RadioButton.ToolTip>
<TextBlock Text="Selecting this option will enable logs at the debug level selected above."/>
</RadioButton.ToolTip>
</RadioButton>
<RadioButton x:Name="DisableLoggingRadioButton" GroupName="Logs" Content="Disable" Margin="5" IsChecked="{Binding Path=IsChecked,ElementName=EnableLoggingRadioButton, Converter={StaticResource oppositebooleanconverter}}" >
<RadioButton.ToolTip>
<TextBlock Text="Selecting this option will disable all logs for the application."/>
</RadioButton.ToolTip>
</RadioButton>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="3">
<Label Content="Log Path" HorizontalAlignment="Left" VerticalAlignment="Center" />
<TextBox Margin="10" Width="347.553" TextWrapping="Wrap" Text="{Binding LogsDirectory}" VerticalAlignment="Stretch" />
<StackPanel Height="100">
<Button x:Name="OpenLogDirectoryButton" Content="Open Directory" Width="100" Margin="0,10,0,0" VerticalAlignment="Center" HorizontalAlignment="Right" Style="{StaticResource ButtonStyle}" d:LayoutOverrides="GridBox" Click="OpenLogDirectoryButton_Click" />
<Button x:Name="ChangeLogDirectoryButton" Content="Change Directory" Width="100" Margin="0,10,0,0" VerticalAlignment="Center" HorizontalAlignment="Right" Style="{StaticResource ButtonStyle}" d:LayoutOverrides="GridBox" Click="ChangeLogDirectoryButton_Click" IsEnabled="False" />
</StackPanel>
</StackPanel>
<Button x:Name="UpdateDebugOptionsButton" Content="Update" Grid.Row="4" Width="100" VerticalAlignment="Center" HorizontalAlignment="Right" Style="{StaticResource ButtonStyle}" Click="UpdateDebugOptionsButton_Click" Margin="0,0,8,10" />
</Grid>
</Border>
Stacktrace BEFORE exception thrown
Merlin.exe!Merlin.App.LogsEnabled.set(bool value = false) Line 52 C#
[External Code]
Merlin.exe!Merlin.View.DebugOptions.DebugOptions() Line 25 + 0x8 bytes C#
[External Code]
Merlin.exe!Merlin.View.TestView.TestView() Line 24 + 0x8 bytes C#
Merlin.exe!Merlin.MainWindow.SidebarButtonsClickHandler(object sender = {Merlin.ImageButton}, System.Windows.RoutedEventArgs e = {System.Windows.RoutedEventArgs}) Line 218 + 0x15 bytes C#
[External Code]
What's odd is that during the initializecomponent routine the "LogsEnabled" boolean value is gotten and then immediately it calls to set it. I have no idea what's calling it to set it. But as soon as it sets the value it tries to get it again. I'm sure the runtime is throwing the stackoverflow to prevent an infinite loop. So how can I figure out why it wants to do this?
The stack overflow is probably as a result of a circularly-defined reference: it looks like one way this might happen is that your slider control is bound to DebugLevel. However, when you enter the update method, it defines the value of the DebugLevel to that of the slider control.
So you might get something like:
Slider control's value? Oh - I'll go look up DebugLevel.
DebugLevel's value? Oh, I'll go look up slider control's value.
Slider control's value? Oh - I'll go look up DebugLevel.
I'm not certain, but that could be the problem.
(like the above commenter mentioned, a stack trace would be really helpful here)

WPF: How to load lots of large images fast into wrappanel?

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.

Categories

Resources