双向绑定在ContentControl中不起作用,绑定到泛型值

时间:2016-05-29 16:11:21

标签: c# wpf generics data-binding user-controls

我正在尝试构建一个允许编辑列表中项目的用户控件。 首先,我将向您展示我的代码和代码下面我将解释我的问题。如果要重现该问题,可以创建一个新项目并将所有代码复制到其中。我将包括所需的每一堂课。

用户控件将绑定到的列表代码:

using System.Collections.Generic;

    namespace TestApplicationWPF
    {
      public class SettingList : List<ISetting>
      {
      }
    }

界面ISetting:

namespace TestApplicationWPF
{
  public class ISetting
  {
    string Name { get; set; }
  }
}

我写入列表的通用对象:

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace TestApplicationWPF
{
  public class Setting<T> : ISetting, INotifyPropertyChanged
  {
    public string Name { get; set; }

    private T value;

    public T Value
    {
      get
      {
        return value;
      }
      set
      {
        this.value = value;
        OnPropertyChanged();
      }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
  }
}

现在,用户控件的目标是绑定到SettingList的一个实例,并允许用户编辑列表中每个“Setting”-instance的“Value”属性。为此,用户控件根据设置中的T类型显示特定控件。例如,String显示在Textbox中,DateTime-Value将显示在DatePicker中。

用户控件的代码如下所示:

<UserControl x:Class="TestApplicationWPF.SettingEditor"
             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" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:TestApplicationWPF"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
  <UserControl.Resources>
    <local:TypeOfConverter x:Key="TypeOfConverter" />
    <Style x:Key="TypedValueStyle" TargetType="{x:Type ContentControl}">
      <Setter Property="Width" Value="200" />
      <Setter Property="ContentTemplate">
        <Setter.Value>
          <DataTemplate>
            <TextBox Text="{Binding Path=.}" IsReadOnly="true" Background="LightGray"/>
          </DataTemplate>
        </Setter.Value>
      </Setter>
      <Style.Triggers>
        <DataTrigger Binding="{Binding Converter={StaticResource TypeOfConverter},Path=Value}" 
                     Value="String">
          <Setter Property="ContentTemplate">
            <Setter.Value>
              <DataTemplate>
                <TextBox Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content, Mode=TwoWay}"                  
                         HorizontalAlignment="Stretch"/>
              </DataTemplate>
            </Setter.Value>
          </Setter>
        </DataTrigger>
      </Style.Triggers>
    </Style>
  </UserControl.Resources>
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="80*"/>
      <RowDefinition Height="20*"/>
      <RowDefinition Height="20*"/>
    </Grid.RowDefinitions>
    <ListView Name="LvPluginSettings" 
              ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SettingEditor}}, Path=Settings, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
              Grid.Row="0">
      <ListView.View>
        <GridView>
          <GridViewColumn Header="Name" Width="100">
            <GridViewColumn.CellTemplate>
              <DataTemplate>
                <Label Content="{Binding Path=Name}"/>
              </DataTemplate>
            </GridViewColumn.CellTemplate>
          </GridViewColumn>
          <GridViewColumn Header="Value" 
                          Width="Auto">
            <GridViewColumn.CellTemplate>
              <DataTemplate>
                <ContentControl Content="{Binding Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource TypedValueStyle}" />
              </DataTemplate>
            </GridViewColumn.CellTemplate>
          </GridViewColumn>
        </GridView>
      </ListView.View>
    </ListView>
    <Button Name ="BtnSetValue" Grid.Row="1" Content="Set the value" Click="BtnSetValue_Click"/>
    <Button Name ="BtnGetValue" Grid.Row="2" Content="What's the value?" Click="BtnGetValue_Click"/>
  </Grid>
</UserControl>

用户控件的.cs代码:

using System.Windows;
using System.Windows.Controls;

namespace TestApplicationWPF
{
  public partial class SettingEditor : UserControl
  {

    public SettingList Settings
    {
      get { return (SettingList)GetValue(PluginSettingsProperty); }
      set { SetValue(PluginSettingsProperty, value); }
    }

    public static readonly DependencyProperty PluginSettingsProperty = DependencyProperty.Register(
                "Settings",
                typeof(SettingList),
                typeof(SettingEditor),
                new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)
    );

    public SettingEditor()
    {
      InitializeComponent();
    }

    private void BtnSetValue_Click(object sender, RoutedEventArgs e)
    {
      ((Setting<string>)Settings[0]).Value = "Different value now.";
    }

    private void BtnGetValue_Click(object sender, RoutedEventArgs e)
    {
      MessageBox.Show(((Setting<string>)Settings[0]).Value);
    }
  }
}

为了确定列表中每个项目的“Value”属性的类型,我使用了“TypeOfConverter”。转换器看起来像这样:

using System;
using System.Windows.Data;

namespace TestApplicationWPF
{
  public class TypeOfConverter : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
      return (value == null) ? null : value.GetType().Name;
    }
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
      throw new NotImplementedException();
    }
  }
}

最后,为了能够完全重现这个问题,我给你使用了用户控件的MainWindow和它的ViewModel: 窗口:

<Window x:Class="TestApplicationWPF.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:local="clr-namespace:TestApplicationWPF"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
  <Window.Resources>
    <local:MainWindowViewModel x:Key="MainWindowViewModel" />
  </Window.Resources>
    <Grid DataContext="{StaticResource MainWindowViewModel}">
    <local:SettingEditor Settings="{Binding Path=Settings}" />
  </Grid>
</Window>

视图模型:

namespace TestApplicationWPF
{
  public class MainWindowViewModel
  {
    public SettingList Settings { get; set; }

    public MainWindowViewModel()
    {
      Settings = new SettingList();
      Settings.Add(new Setting<string> { Name="Name of setting", Value = "HelloWorld" });
    }

  }
}

我的问题是设置实例的Value-Property的绑定。当我启动应用程序时,它将向我显示完美的价值。我得到一个带有“HelloWorld”的文本框。此外,当值在后台更改时,它将更新到文本框。 但是,当我将光标设置到文本框中时,将文本更改为其他内容并保留文本框,它将不会在绑定的“值”-Property中更改。此外,在我尝试编辑文本框中的文本后,在后台进行的更改不再影响文本框。

如果有人能帮助我,我将深表感激。即使是可能出错的最小暗示也会对我有所帮助。

问候, 斯文

2 个答案:

答案 0 :(得分:1)

欢迎来到SO!

这是一种利用ObservableCollectionDataTemplateSelector和一些C#字幕的方法:D

enter image description here

备注:

  • 使用ObservableCollection<T>通知订阅者其中的更改
  • 没有使用UserControl,而是使用ItemsControl代替
  • Setting有目的地抽象,强制执行Setting<T>用法
  • 可观察集合包含Setting个对象
  • 项目演示者使用模板选择器,该模板选择器根据第一个泛型类型参数
  • 返回正确的模板

等...使用代码并看到它有效地工作而不会很麻烦:D

<强> C#

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;

namespace WpfApplication4
{
    public partial class MainWindow
    {
        public MainWindow()
        {
            InitializeComponent();

            var collection =
                new SettingCollection(new Setting[]
                {
                    new Setting<bool> {Name = "boolean"},
                    new Setting<string> {Name = "string"}
                });
            DataContext = collection;
        }

        private void Button1_Click(object sender, RoutedEventArgs e)
        {
            var collection = (SettingCollection) DataContext;
            var setting = collection.OfType<Setting<bool>>().FirstOrDefault();
            if (setting != null)
            {
                setting.Value = !setting.Value;
            }
        }

        private void Button2_Click(object sender, RoutedEventArgs e)
        {
            var collection = (SettingCollection) DataContext;
            var setting = collection.OfType<Setting<string>>().FirstOrDefault();
            if (setting != null)
            {
                setting.Value = DateTime.Now.ToString(CultureInfo.InvariantCulture);
            }
        }

        private void Button3_Click(object sender, RoutedEventArgs e)
        {
            var collection = (SettingCollection) DataContext;
            Debugger.Break();
        }
    }

    public sealed class SettingCollection : ObservableCollection<Setting>
    {
        public SettingCollection(List<Setting> list) : base(list)
        {
        }

        public SettingCollection(IEnumerable<Setting> collection) : base(collection)
        {
        }

        public SettingCollection()
        {
        }
    }

    public abstract class Setting : INotifyPropertyChanged
    {
        private object _value;
        public string Name { get; set; }

        public object Value
        {
            get { return _value; }
            set
            {
                if (Equals(value, _value)) return;
                _value = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public sealed class Setting<T> : Setting
    {
        private T _value;

        public new T Value
        {
            get { return _value; }
            set
            {
                if (Equals(value, _value)) return;
                _value = value;
                OnPropertyChanged();
            }
        }
    }

    public class SettingTemplateSelector : DataTemplateSelector
    {
        public override DataTemplate SelectTemplate(object item, DependencyObject container)
        {
            var element = container as FrameworkElement;
            if (element != null && item != null)
            {
                var type = item.GetType();
                var types = type.GenericTypeArguments;
                var type1 = types[0];

                if (type1 == typeof(bool))
                {
                    var findResource = element.FindResource("SettingBoolTemplate");
                    var dataTemplate = findResource as DataTemplate;
                    return dataTemplate;
                }

                if (type1 == typeof(string))
                {
                    var findResource = element.FindResource("SettingStringTemplate");
                    var dataTemplate = findResource as DataTemplate;
                    return dataTemplate;
                }
            }
            return null;
        }
    }
}

<强> XAML:

<Window x:Class="WpfApplication4.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:local="clr-namespace:WpfApplication4"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <StackPanel Margin="20">
            <Button Content="Set first Setting&lt;bool&gt; to something" Click="Button1_Click" />
            <Button Content="Set first Setting&lt;string&gt; to something" Click="Button2_Click" />
            <Button Content="Debugger break" Click="Button3_Click"></Button>
            <ItemsControl ItemsSource="{Binding}">
                <ItemsControl.Resources>
                    <DataTemplate x:Key="SettingBoolTemplate" DataType="local:Setting">
                        <CheckBox IsChecked="{Binding Value}" />
                    </DataTemplate>
                    <DataTemplate x:Key="SettingStringTemplate" DataType="local:Setting">
                        <TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
                    </DataTemplate>
                    <local:SettingTemplateSelector x:Key="SettingTemplateSelector" />
                </ItemsControl.Resources>
                <ItemsControl.ItemTemplate>
                    <DataTemplate DataType="local:Setting">
                        <Border Margin="5" BorderBrush="DodgerBlue" Padding="2" BorderThickness="1">
                            <StackPanel>
                                <TextBlock Text="{Binding Name}" />
                                <ContentControl Content="{Binding}"
                                                ContentTemplateSelector="{StaticResource SettingTemplateSelector}" />
                            </StackPanel>
                        </Border>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </StackPanel>

    </Grid>
</Window>

答案 1 :(得分:1)

我发现了问题。

在用户控件中,我有一个ContentControl,如下所示:

presentation

控件的DataTemplate如下所示:

<ContentControl Content="{Binding Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource TypedValueStyle}" />

问题是数据绑定。 ContentControl的Content绑定到我的对象中的Value-property。但是,DataTemplate绑定到ContentControl的Content-property。为了使绑定工作,我需要特别设置数据模板中控件的绑定到Value-property。

现在正在运行的ContentControl如下所示:

<DataTemplate>
      <TextBox Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content, Mode=TwoWay}"                  
               HorizontalAlignment="Stretch"/>
</DataTemplate>

DataTemplate看起来像这样:

<ContentControl DataContext="{Binding}" 
                            Content="{Binding Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
                            Style="{StaticResource TypedValueStyle}" />