views:

64

answers:

1

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?

+1  A: 

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)

phyllis diller
stack trace added. Since LogsEnabled is a clr property I have to manually update it's value during the update event or it doesn't get it's newer value. That's the only way I know how to do it without making numerous copies of a boolean var and assigning logsEnabled's current value to it. I figured binding to the app class property would save me duplicate code but maybe i'm wrong. I tend to back myself into a corner with these types of things in c# because I was able to get away with more in vb. So by all means if i'm not following a good pattern of design let me know i wont be offended.
TWood
No, you're right to bind to app class property here. I'm just not sure I would have located the information in the app class. I would personally put it in a static object accessible to the entire application. Keeping stuff in the App class feels weird to me, in general. I usually use it only for dealing with startup parameters (in the case where I am writing an application that has both a UI and command line interface) and instantiating the main class of my program.
phyllis diller
Your right I should move that out to a static class. I'll have to figure out how to bind to it though.
TWood
You'd set the DataContext in the code-behind - UIElement.DataContext = StaticClassName. Then just bind to any property of that class normally in the xaml.
phyllis diller