-1

I would like to dynamically generate a view of text boxes that shows the names and values of each property in Properties.Settings.Default.

I have tried the following code, but I'm unable to get the path to assign correctly.

foreach (SettingsProperty currentProperty in Properties.Settings.Default.Properties)
{
 TextBox X = new TextBox();
 TextBox Y = new TextBox();
 Grid G = new Grid();
 var x = Properties.Settings.Default[currentProperty.Name];

 X.SetBinding(ItemsControl.ItemsSourceProperty,
     new Binding
     {
         Source = "{x:Static properties:Settings.Default}" + ", Path = " + currentProperty.Name + "}",
         UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
     });

    //{Binding Source={x:Static properties:Settings.Default}, Path = MYSETTING1}

    G.Children.Add(X);
    MyStackPanel.Children.Add(G);

}

I also tried the following:

X.SetBinding(ItemsControl.ItemsSourceProperty,
 new Binding
 {
     Source = "{x:Static properties:Settings.Default}",
     Path = currentProperty.Name,
     UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
 });
Ethan
  • 73
  • 10
  • 1
    There is a bigger question: **Why** would you want to bind them in code? Usually trying soemthing like this indicates you are on the wrong track. WPF is designed with the MVVM pattern in mind and if you use it, you will never need to do this. I wrote a intro into it a few years back: https://social.msdn.microsoft.com/Forums/vstudio/en-US/b1a8bf14-4acd-4d77-9df8-bdb95b02dbe2/lets-talk-about-mvvm?forum=wpf – Christopher Jul 31 '19 at 23:07
  • Some references for mvvm examples. https://stackoverflow.com/questions/14589200/binding-custom-list-object-properties-to-textbox-from-code-behind and https://stackoverflow.com/questions/24017906/bind-textbox-list-inside-listbox-in-wpf. – CharithJ Aug 01 '19 at 01:55

2 Answers2

2

Just going off your initial sentence, you could do something like this:

foreach (SettingsProperty currentProperty in Properties.Settings.Default.Properties)
{
    TextBox X               = new TextBox();
    TextBox Y               = new TextBox();
    Grid G                  = new Grid();
    ColumnDefinition one    = new ColumnDefinition();
    ColumnDefinition two    = new ColumnDefinition();
    one.Width               = new GridLength(50, GridUnitType.Star);
    two.Width               = new GridLength(50, GridUnitType.Star);
    G.ColumnDefinitions.Add(one);
    G.ColumnDefinitions.Add(two);
    Grid.SetColumn(X, 0);
    Grid.SetColumn(Y, 1);

    X.SetBinding(TextBox.TextProperty,
        new Binding
        {
            Source = currentProperty.Name,
            Path = new PropertyPath("."),
            UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
        });

    Y.SetBinding(TextBox.TextProperty,
        new Binding
        {
            Source = currentProperty.DefaultValue,
            Path = new PropertyPath("."),
            UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
        });

    G.Children.Add(X);
    G.Children.Add(Y);
    MyStackPanel.Children.Add(G);
}
1

There are lots of ways you could do this. But as hinted at by this comment:

There is a bigger question: Why would you want to bind them in code? Usually trying soemthing like this indicates you are on the wrong track. WPF is designed with the MVVM pattern in mind and if you use it, you will never need to do this.

…you should go ahead and use regular binding. WPF makes this relatively simple, and it can be implemented entirely in XAML:

<Window x:Class="TestSO57299808PropertyBrowser.MainWindow"
        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"
        xmlns:properties="clr-namespace:TestSO57299808PropertyBrowser.Properties"
        xmlns:configuration="clr-namespace:System.Configuration;assembly=System"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
  <ListBox ItemsSource="{Binding PropertyValues, Source={x:Static properties:Settings.Default}}">
    <ListBox.ItemTemplate>
      <DataTemplate DataType="{x:Type configuration:SettingsPropertyValue}">
        <Grid ShowGridLines="True">
          <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
          </Grid.ColumnDefinitions>
          <TextBlock Text="{Binding Name}" Padding="5,0" Grid.Column="0"/>
          <TextBlock Text="{Binding PropertyValue}" Padding="5,0" Grid.Column="1"/>
        </Grid>
      </DataTemplate>
    </ListBox.ItemTemplate>
  </ListBox>
</Window>

The above puts a ListBox into the window, sets its ItemsSource property to reference the Settings.Default.PropertyValues collection, and declares a DataTemplate to use to display the property name and value for each SettingsPropertyValue object contained in that collection.

Actually, I lied. There is one line of code-behind required, because the .NET Settings object has a little bug. The PropertyValues collection will be empty until the first time any property value is retrieved. So you need to make sure to retrieve a property value during initialization (any property will do). For example, in the window constructor:

    public MainWindow()
    {
        var _ = Properties.Settings.Default.Property1;
        InitializeComponent();
    }

See Why does Settings PropertyValues have 0 items? for some discussion of that bug.

Keep in mind that whether you copy values in code-behind or use binding as above, you will need to handle your own updates if/when a property values changes, because the collection itself doesn't implement INotifyCollectionChanged and so WPF has no way to know if a property value has updated. You can use BindingOperations and BindingExpression to force the collection binding to update, if you use data binding as shown here.

However, doing so would be a bit of a kludge, since you'd be telling WPF to update the entire collection every time just one property changed. If you expect the property values to be changing and want that reflected in the UI, it would be better to implement a proxy that can provide a proper collection of property name/value pairs along with property-change notification. For example:

class ViewModel
{
    public IReadOnlyList<SettingsPropertyValueProxy> Values { get; } = Array.AsReadOnly(
            Properties.Settings.Default.Properties
            .Cast<SettingsProperty>()
            .Select(p => new SettingsPropertyValueProxy(p.Name))
            .OrderBy(p => p.Name)
            .ToArray());
}

class SettingsPropertyValueProxy : INotifyPropertyChanged
{
    public string Name { get; }
    public object PropertyValue => Properties.Settings.Default[Name];

    public SettingsPropertyValueProxy(string name)
    {
        Name = name;
        Properties.Settings.Default.PropertyChanged += (sender, e) => _OnPropertyChanged(e.PropertyName);
    }

    private void _OnPropertyChanged(string propertyName)
    {
        if (propertyName == Name) PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(PropertyValue)));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Then in the XAML, bind to the view model instead of the settings object directly:

<Window x:Class="TestSO57299808PropertyBrowser.MainWindow"
        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"
        xmlns:l="clr-namespace:TestSO57299808PropertyBrowser"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
  <Window.DataContext>
    <l:ViewModel/>
  </Window.DataContext>

  <ListBox ItemsSource="{Binding Values}">
    <ListBox.ItemTemplate>
      <DataTemplate DataType="{x:Type l:SettingsPropertyValueProxy}">
        <Grid ShowGridLines="True">
          <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
          </Grid.ColumnDefinitions>
          <TextBlock Text="{Binding Name}" Padding="5,0" Grid.Column="0"/>
          <TextBlock Text="{Binding PropertyValue}" Padding="5,0" Grid.Column="1"/>
        </Grid>
      </DataTemplate>
    </ListBox.ItemTemplate>
  </ListBox>
</Window>

The only difference above from the earlier example is that the data context is set to an instance of the view model, and then the view model's Values property is used for the ItemsSource instead of the Settings.Default.PropertyValues property.

Note that in this approach, there's no need for the little hack to trigger population of the PropertyValues collection, as this version doesn't rely on that collection at all.

This is a far better approach than writing code-behind to create UI objects and manually implement their bindings. Doing this in XAML, with just the barest of code-behind plumbing to wrap the settings in a WPF-friendly way, ensures proper separation of UI from the underlying data, and makes it far easier to make changes to the UI itself as desired.

Finally, if this is more than just an academic exercise, note that there are already existing solutions to display the properties of an object. See e.g. wpf propertyGrid and Analogous WinForms Propertygrid in WPF?

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136