简体   繁体   English

如何在代码中将TextBoxes绑定到Properties.Settings.Default中的每个属性?

[英]How can I bind TextBoxes to each property in Properties.Settings.Default in code?

I would like to dynamically generate a view of text boxes that shows the names and values of each property in Properties.Settings.Default . 我想动态生成一个文本框视图,该视图显示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
 });

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

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. WPF在设计时就考虑了MVVM模式,如果您使用它,则永远不需要这样做。

…you should go ahead and use regular binding. …您应该继续使用常规绑定。 WPF makes this relatively simple, and it can be implemented entirely in XAML: WPF使这相对简单,并且可以完全在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. 上面的代码将一个ListBox放入窗口,将其ItemsSource属性Settings.Default.PropertyValues为引用Settings.Default.PropertyValues集合,并声明一个DataTemplate用于显示该集合中包含的每个SettingsPropertyValue对象的属性名称和值。

Actually, I lied. 其实我说谎 There is one line of code-behind required, because the .NET Settings object has a little bug. 因为.NET Settings对象有一个小错误,所以仅需要一行代码。 The PropertyValues collection will be empty until the first time any property value is retrieved. 在首次检索任何属性值之前, PropertyValues集合将为空。 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? 请参阅为什么设置PropertyValues有0个项目? 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. 请记住,无论是在代码隐藏中复制值还是如上所述使用绑定,如果/当属性值更改时,您都将需要处理自己的更新,因为集合本身没有实现INotifyCollectionChanged ,因此WPF无法实现知道属性值是否已更新。 You can use BindingOperations and BindingExpression to force the collection binding to update, if you use data binding as shown here. 如果使用如下所示的数据绑定,则可以使用BindingOperationsBindingExpression强制更新集合绑定。

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. 但是,这样做会有点麻烦,因为您将告诉WPF每次仅更改一个属性就更新整个集合。 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. 如果您希望属性值发生变化并希望在UI中反映出来,那么最好实现一个代理,该代理可以提供正确的属性名称/值对集合以及属性更改通知。 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: 然后在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: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. 上面与先前示例的唯一区别是,将数据上下文设置为视图模型的实例,然后将视图模型的Values属性用于ItemsSource而不是Settings.Default.PropertyValues属性。

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. 请注意,在这种方法中,无需任何技巧即可触发PropertyValues集合的填充,因为此版本完全不依赖该集合。

This is a far better approach than writing code-behind to create UI objects and manually implement their bindings. 这比编写后台代码创建UI对象并手动实现其绑定好得多。 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. 在XAML中执行此操作,仅需执行代码隐藏操作即可以一种WPF友好的方式包装设置,从而确保将UI与基础数据正确分离,并使按需更改UI本身变得容易得多。

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 eg wpf propertyGrid and Analogous WinForms Propertygrid in WPF? 参见WPF中的wpf propertyGrid类似WinForms Propertygrid吗?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM